diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..f045556 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# IntelliJ project files +.idea diff --git a/BaseSchemaRules.coffee b/BaseSchemaRules.coffee new file mode 100755 index 0000000..74af63a --- /dev/null +++ b/BaseSchemaRules.coffee @@ -0,0 +1,34 @@ +log = new ObjectLogger("practical.BaseSchema", "debug") + + +@practical.BaseSchemaRules = -> + return { + # https://github.com/aldeed/meteor-collection2#autovalue + # Force value to be current date (on server) upon insert + # and prevent updates thereafter. + createdAt: + type: Date + index: 1 + autoValue: -> + if @isInsert + return lv.util.getCurrentDate() + else if @isUpsert + return {$setOnInsert: lv.util.getCurrentDate()} + else + this.unset() + denyUpdate: true + + # https://github.com/aldeed/smeteor-collection2#autovalue + # Force value to be current date (on server) upon update. + # On insert, it will always be set to the insert date. + modifiedAt: + type: Date + index: 1 + autoValue: -> + if @isUpdate + return lv.util.getCurrentDate() + else if @isUpsert + return {$set: lv.util.getCurrentDate()} + denyInsert: true + optional: true + } diff --git a/CollectionInit.coffee b/CollectionInit.coffee new file mode 100755 index 0000000..8233d4f --- /dev/null +++ b/CollectionInit.coffee @@ -0,0 +1,106 @@ +log = new ObjectLogger("practical.CollectionInit", "debug") +practical = @practical + +class @CollectionInit + + + @init: (collection, opts = {})=> + try + log.enter("init", opts) + expect(collection, "collection is not a Mongo Collection").to.be.instanceOf(Mongo.Collection) + expect(opts, "opts").to.be.an("object") + + collection.practical = {} + + @_createSchema(collection, opts) + + if opts.autopublish + @_autoPublishCollection(collection) + + # Custom publish and subscription handlers + if opts.sub and opts.pub and not opts.autopublish + @_customSubAndPub(collection, opts) + + if opts.populate? and Meteor.isServer + expect(opts.populate, "populate").to.be.a("function") + opts.populate() + + if opts.indexes? and Meteor.isServer + @_ensureIndexes(collection, opts) + + + + finally + log.return() + + + @_createSchema: (collection, opts)=> + try + log.enter("_createSchema", opts) + expect(collection).to.be.instanceOf(Mongo.Collection) + expect(collection.practical).to.be.an("object") + + schemaRules = practical.BaseSchemaRules() + + if opts.schemaRules? + expect(opts.schemaRules).to.be.an("object") + schemaRules = _.extend(schemaRules, opts.schemaRules) + + collection.practical.schemaRules = schemaRules + collection.practical.schema = new SimpleSchema(schemaRules) + collection.attachSchema(collection.practical.schema) + + + finally + log.return() + + + @_autoPublishCollection: (collection)-> + try + log.enter("_autoPublishCollection") + expect(collection).to.be.instanceOf(Mongo.Collection) + expect(collection.practical).to.be.an("object") + + if Meteor.isServer + Meteor.publish collection._name, -> + return collection.find({}) + + if Meteor.isClient + collection.practical.subHandle = Meteor.subscribe collection._name,{ + onReady: -> + onError: (error)-> + log.error("Error with subscription [#{collection._name}]", error) if error + } + + finally + log.return() + + + @_customSubAndPub: (collection, opts)=> + try + log.enter("_customSubAndPub", opts) + + if Meteor.isServer + expect(opts.pub, "custom publication").to.be.a("function") + opts.pub() + + if Meteor.isClient + expect(opts.sub, "custom subscription").to.be.a("function") + opts.sub() + + finally + log.return() + + + @_ensureIndexes: (collection, opts)=> + try + log.enter("_ensureIndexes", opts) + expect(Meteor.isServer).to.be.true + expect(opts.indexes).to.be.an("array") + + for index in opts.indexes + expect(index.keys).to.be.an("object") + expect(index.options).to.be.an("object") + collection._ensureIndex(index.keys, index.options) + finally + log.return() \ No newline at end of file diff --git a/bin/ci-test b/bin/ci-test new file mode 100755 index 0000000..494eaa1 --- /dev/null +++ b/bin/ci-test @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +spacejam test-packages --driver-package practicalmeteor:mocha-console-runner ./ diff --git a/bin/test b/bin/test new file mode 100755 index 0000000..e7e1186 --- /dev/null +++ b/bin/test @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +unset MONGO_URL +meteor test-packages --driver-package practicalmeteor:mocha ./ --port 3100 diff --git a/circle.yml b/circle.yml new file mode 100755 index 0000000..9d43b36 --- /dev/null +++ b/circle.yml @@ -0,0 +1,12 @@ +machine: + node: + version: 0.10.40 + +dependencies: + pre: + - curl https://install.meteor.com/ | sh + - npm install -g spacejam + +test: + override: + - ./bin/ci-test \ No newline at end of file diff --git a/package.js b/package.js new file mode 100755 index 0000000..b7d0ed8 --- /dev/null +++ b/package.js @@ -0,0 +1,43 @@ +Package.describe({ + name: "practicalmeteor:collection-init", + summary: "write package tests with mocha and run them in the browser or from the command line with spacejam.", + git: "https://github.com/practicalmeteor/meteor-mocha.git", + version: '0.1.0' +}); + + +Package.onUse(function(api){ + + // TODO add specific packages versions + // Meteor packages + api.use("coffeescript"); + api.use("mongo"); + api.use("underscore"); + + // Practical Meteor packages + api.use("practicalmeteor:core"); + + // Vendor packages + api.use("aldeed:simple-schema"); + api.use("aldeed:collection2@2.5.0"); + + api.imply("aldeed:simple-schema@1.3.3"); + api.imply("aldeed:collection2@2.5.0"); + + // Files + api.addFiles("CollectionInit.coffee") + api.addFiles("BaseSchemaRules.coffee") + +}); + +Package.onTest(function (api) { + + api.use("coffeescript"); + api.use("mongo"); + api.use("underscore"); + + api.use("practicalmeteor:collection-init"); + api.use("practicalmeteor:loglevel"); + + api.addFiles("tests/CollectionInitTests.coffee"); +}); \ No newline at end of file diff --git a/tests/CollectionInitTests.coffee b/tests/CollectionInitTests.coffee new file mode 100755 index 0000000..b954f95 --- /dev/null +++ b/tests/CollectionInitTests.coffee @@ -0,0 +1,148 @@ +log = new ObjectLogger("practical.CollectionInitTests") + +describe "CollectionInit", -> + + collectionName = "collection" + collection = new Mongo.Collection(collectionName) + + beforeEach -> + + delete collection.practical + + spies.create("Meteor.subscribe", Meteor, "subscribe") if Meteor.isClient + + if Meteor.isServer + spies.create("Meteor.publish", Meteor, "publish") + + stubs.create("collection.ensureIndex", collection, "_ensureIndex") + + + it "Add 'practical' property to the collection", -> + + CollectionInit.init(collection) + expect(collection.practical).to.be.an("object") + + + it "Create publication and subscription for collection is autopublish is true ", -> + + opts = { + autopublish: true + } + + CollectionInit.init(collection, opts) + + if Meteor.isClient + # Save subscription handle into practical + expect(collection.practical.subHandle).to.be.an("object") + expect(spies["Meteor.subscribe"]).to.have.been.called + expect(spies["Meteor.subscribe"].args[0][0]).to.equals(collectionName) + + if Meteor.isServer + expect(spies["Meteor.publish"]).to.have.been.called + expect(spies["Meteor.publish"].args[0][0]).to.equals(collectionName) + + + it "Calls custom sub and pub if not autopublish", -> + + opts = { + sub: stubs.create("sub") + pub: stubs.create("pub") + } + + CollectionInit.init(collection, opts) + if Meteor.isClient + expect(stubs.sub).to.have.been.called + + if Meteor.isServer + expect(stubs.pub).to.have.been.called + + it "Don't calls custom sub and pub if not autopublish", -> + + opts = { + autopublish: true + sub: stubs.create("sub") + pub: stubs.create("pub") + } + + CollectionInit.init(collection, opts) + + if Meteor.isClient + expect(stubs.sub).not.to.have.been.called + + if Meteor.isServer + expect(stubs.pub).not.to.have.been.called + + it "Calls populate function", -> + + opts = { + populate: stubs.create("populate") + } + CollectionInit.init(collection, opts) + + if Meteor.isServer + expect(stubs.populate).to.have.been.called + if Meteor.isClient + expect(stubs.populate).not.to.have.been.called + + it "create indexes for the collection", -> + + opts = { + indexes:[ + { + keys: {name: 1} + options: {unique: true} + }, + { + keys: {title: 1} + options: {} + } + ] + } + + CollectionInit.init(collection, opts) + + if Meteor.isServer + expect(stubs["collection.ensureIndex"]).to.have.been.callCount(opts.indexes.length) + for index in opts.indexes + expect(stubs["collection.ensureIndex"]).to.have.been.calledWith(index.keys, index.options) + + if Meteor.isClient + expect(stubs["collection.ensureIndex"]).not.to.have.been.called + + + it "Attach base schema to collection", -> + + opts = { + } + + CollectionInit.init(collection, opts) + + expect(collection.practical.schema).to.be.instanceOf(SimpleSchema) + expect(_.keys(collection.practical.schemaRules)).to.eql(_.keys(practical.BaseSchemaRules())) + + # I'm using a private property to determinate the keys of the schema of the collection, + # this can change and made the tests to fail in next releases of the package + expect(_.keys(collection.practical.schemaRules)).to.eql(collection.practical.schema._schemaKeys) + + + it "Attach base schema and other schema to the collection", -> + + opts = { + schemaRules: { + name: + type: String + } + } + expectKeys = _.keys(practical.BaseSchemaRules()).concat(_.keys(opts.schemaRules)) + + + CollectionInit.init(collection, opts) + + + expect(collection.practical.schema).to.be.instanceOf(SimpleSchema) + expect(_.keys(collection.practical.schemaRules)).to.eql(expectKeys) + + # I'm using a private property to determinate the keys of the schema of the collection, + # this can change and made the tests to fail in next releases of the package + expect(_.keys(collection.practical.schemaRules)).to.eql(collection.practical.schema._schemaKeys) +