-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 2ed7322
Showing
9 changed files
with
384 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
node_modules |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"extends": "lookback/meteor", | ||
|
||
"globals": { | ||
"PublicationCollector": true | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
node_modules/ | ||
npm-debug.log | ||
.DS_Store | ||
.atomignore |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
# Publication Collector | ||
|
||
This package makes testing publications in Meteor easier and nicer. | ||
|
||
Instead of resorting to exporting or exposing your publication functions for doing testing, this package lets you "subscribe" to a given publication and assert its returned results. | ||
|
||
## Installation | ||
|
||
``` | ||
meteor add johanbrook:publication-collector | ||
``` | ||
|
||
## Usage | ||
|
||
```js | ||
// In a typical BDD style test suite: | ||
|
||
describe('Publication', function() { | ||
if('should publish 10 documents', function() { | ||
// Pass user context in constructor. | ||
const collector = new PublicationCollector({userId: Random.id()}); | ||
|
||
// Collect documents from a subscription with 'collect(name, [arguments...], [callback])' | ||
collector.collect('publicationName', 'someArgument', (collections) => { | ||
assert.equal(collections.myCollection.length, 10); | ||
}); | ||
}); | ||
}); | ||
``` | ||
|
||
An instance of `PublicationCollector` also is an `EventEmitter`, and emits a `ready` event when the publication is marked as ready. | ||
|
||
## Tests | ||
|
||
Run tests once with | ||
|
||
``` | ||
npm test | ||
``` | ||
|
||
Run tests in watch mode (in console) with | ||
|
||
``` | ||
npm run test:dev | ||
``` | ||
|
||
## History | ||
|
||
This project was originally a part of MDG's [Todos](https://github.com/meteor/todos) example Meteor app, but later extracted as a separate test package. | ||
|
||
Based on https://github.com/stubailo/meteor-rest/blob/devel/packages/rest/http-subscription.js. | ||
|
||
## To do | ||
|
||
- [ ] Make tests pass. | ||
- [ ] More docs. | ||
- [ ] Support Promises. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
/* eslint-disable prefer-arrow-callback */ | ||
|
||
Package.describe({ | ||
name: 'johanbrook:publication-collector', | ||
version: '1.0.0', | ||
summary: 'Test a Meteor publication by collecting its output.', | ||
documentation: 'README.md', | ||
git: 'https://github.com/johanbrook/publication-collector.git', | ||
debugOnly: true | ||
}); | ||
|
||
Package.onUse(function(api) { | ||
api.versionsFrom('1.2.0.2'); | ||
|
||
api.use([ | ||
'ecmascript', | ||
'underscore', | ||
'check' | ||
], 'server'); | ||
|
||
api.addFiles('publication-collector.js', 'server'); | ||
|
||
api.export('PublicationCollector', 'server'); | ||
}); | ||
|
||
Package.onTest(function(api) { | ||
api.use([ | ||
'ecmascript', | ||
'mongo', | ||
'random', | ||
'dispatch:mocha', | ||
'practicalmeteor:sinon', | ||
'practicalmeteor:chai@2.1.0_1', | ||
'underscore' | ||
], 'server'); | ||
|
||
api.use('johanbrook:publication-collector'); | ||
|
||
api.addFiles([ | ||
'tests/publications.js', | ||
'tests/publication-collector.test.js' | ||
], 'server'); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
{ | ||
"name": "publication-collector", | ||
"version": "1.0.0", | ||
"description": "Test a Meteor publication by collecting its output.", | ||
"scripts": { | ||
"test": "SERVER_TEST_REPORTER='dot' meteor test-packages --once --driver-package dispatch:mocha ./", | ||
"test:dev": "TEST_WATCH=1 meteor test-packages --driver-package dispatch:mocha ./", | ||
"lint": "eslint --format=node_modules/eslint-formatter-pretty ." | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/johanbrook/publication-collector.git" | ||
}, | ||
"license": "MIT", | ||
"bugs": { | ||
"url": "https://github.com/johanbrook/publication-collector/issues" | ||
}, | ||
"devDependencies": { | ||
"babel-preset-es2015": "^6.3.13", | ||
"eslint": "^2.2.0", | ||
"eslint-config-lookback": "lookback/eslint-config-lookback", | ||
"eslint-formatter-pretty": "^0.2.0" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
const EventEmitter = Npm.require('events').EventEmitter; | ||
|
||
/* | ||
This file describes something like Subscription in | ||
meteor/meteor/packages/ddp/livedata_server.js, but instead of sending | ||
over a socket it just collects data. | ||
*/ | ||
|
||
PublicationCollector = function(context = {}) { | ||
check(context.userId, Match.Optional(String)); | ||
|
||
// Object where the keys are collection names, and then the keys are _ids | ||
this.responseData = {}; | ||
|
||
this.userId = context.userId; | ||
}; | ||
|
||
// So that we can listen to ready event in a reasonable way | ||
Meteor._inherits(PublicationCollector, EventEmitter); | ||
|
||
_.extend(PublicationCollector.prototype, { | ||
collect(name, ...args) { | ||
if (_.isFunction(args[args.length - 1])) { | ||
this.on('ready', args.pop()); | ||
} | ||
|
||
const handler = Meteor.server.publish_handlers[name]; | ||
const result = handler.call(this, ...args); | ||
|
||
// TODO -- we should check that result has _publishCursor? What does _runHandler do? | ||
if (result) { | ||
// array-ize | ||
[].concat(result).forEach(cur => cur._publishCursor(this)); | ||
this.ready(); | ||
} | ||
}, | ||
|
||
added(collection, id, fields) { | ||
check(collection, String); | ||
check(id, String); | ||
|
||
this._ensureCollectionInRes(collection); | ||
|
||
// Make sure to ignore the _id in fields | ||
const addedDocument = _.extend({_id: id}, _.omit(fields, '_id')); | ||
this.responseData[collection][id] = addedDocument; | ||
}, | ||
|
||
changed(collection, id, fields) { | ||
check(collection, String); | ||
check(id, String); | ||
|
||
this._ensureCollectionInRes(collection); | ||
|
||
const existingDocument = this.responseData[collection][id]; | ||
const fieldsNoId = _.omit(fields, '_id'); | ||
_.extend(existingDocument, fieldsNoId); | ||
|
||
// Delete all keys that were undefined in fields (except _id) | ||
_.forEach(fields, (value, key) => { | ||
if (value === undefined) { | ||
delete existingDocument[key]; | ||
} | ||
}); | ||
}, | ||
|
||
removed(collection, id) { | ||
check(collection, String); | ||
check(id, String); | ||
|
||
this._ensureCollectionInRes(collection); | ||
|
||
delete this.responseData[collection][id]; | ||
|
||
if (_.isEmpty(this.responseData[collection])) { | ||
delete this.responseData[collection]; | ||
} | ||
}, | ||
|
||
ready() { | ||
this.emit('ready', this._generateResponse()); | ||
}, | ||
|
||
onStop() { | ||
// no-op | ||
}, | ||
|
||
stop() { | ||
// no-op | ||
}, | ||
|
||
error(error) { | ||
throw error; | ||
}, | ||
|
||
_ensureCollectionInRes(collection) { | ||
this.responseData[collection] = this.responseData[collection] || {}; | ||
}, | ||
|
||
_generateResponse() { | ||
const output = {}; | ||
|
||
_.forEach(this.responseData, (documents, collectionName) => { | ||
output[collectionName] = _.values(documents); | ||
}); | ||
|
||
return output; | ||
} | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
/* eslint-env mocha */ | ||
/* global Documents, spies */ | ||
|
||
const { assert } = Package['practicalmeteor:chai']; | ||
|
||
PublicationCollector = Package['johanbrook:publication-collector'].PublicationCollector; | ||
|
||
describe('PublicationCollector', () => { | ||
|
||
beforeEach(() => { | ||
Documents.remove({}); | ||
_.times(10, () => Documents.insert({foo: 'bar'})); | ||
}); | ||
|
||
it('should be able to instantiate', () => { | ||
const instance = new PublicationCollector(); | ||
assert.ok(instance); | ||
}); | ||
|
||
describe('Collect', () => { | ||
|
||
it('should collect documents from a publication', () => { | ||
const collector = new PublicationCollector(); | ||
|
||
collector.collect('publication', collections => { | ||
assert.ok(collections.documents); | ||
assert.equal(collections.documents.length, 10, 'collects 10 documents'); | ||
}); | ||
}); | ||
|
||
it('should pass the correct scope to the publication', () => { | ||
const collector = new PublicationCollector({userId: 'foo'}); | ||
|
||
collector.collect('publicationWithUser', collections => { | ||
assert.ok(collections.documents); | ||
assert.equal(collections.documents.length, 10, 'collects 10 documents'); | ||
}); | ||
}); | ||
|
||
it('should emit ready event', () => { | ||
const collector = new PublicationCollector(); | ||
|
||
collector.on('ready', spies.create('ready')); | ||
|
||
collector.collect('publication'); | ||
assert.ok(spies.ready.calledOnce, 'ready was called'); | ||
}); | ||
|
||
it('should pass arguments to publication', (done) => { | ||
Meteor.publish('publicationWithArgs', function(arg1, arg2) { | ||
assert.equal(arg1, 'foo'); | ||
assert.equal(arg2, 'bar'); | ||
this.ready(); | ||
done(); | ||
}); | ||
|
||
const collector = new PublicationCollector(); | ||
|
||
collector.collect('publicationWithArgs', 'foo', 'bar'); | ||
}); | ||
}); | ||
|
||
describe('Added', () => { | ||
|
||
it('should add a document to the local data store', () => { | ||
const collector = new PublicationCollector(); | ||
|
||
const id = Random.id(); | ||
const doc = {_id: id, foo: 'bar'}; | ||
collector.added('documents', doc._id, doc); | ||
|
||
assert.deepEqual(collector.responseData.documents[id], doc); | ||
}); | ||
}); | ||
|
||
describe('Removed', () => { | ||
|
||
it('should remove a document to the local data store', () => { | ||
const collector = new PublicationCollector(); | ||
|
||
const doc = Documents.findOne(); | ||
collector.collect('publication'); | ||
collector.removed('documents', doc._id); | ||
|
||
assert.notOk(collector.responseData.documents[doc._id]); | ||
assert.equal(Object.keys(collector.responseData.documents).length, 9); | ||
}); | ||
}); | ||
|
||
describe('Error', () => { | ||
|
||
it('should throw error when .error() is called in publication', () => { | ||
// We're not passing a user context here to trigger error. | ||
const collector = new PublicationCollector(); | ||
|
||
assert.throws(() => { | ||
collector.collect('publicationError'); | ||
}, Meteor.Error, /Not authorized/); | ||
}); | ||
}); | ||
|
||
describe('_generateResponse', () => { | ||
|
||
it('should generate a response with collection names as keys', () => { | ||
const collector = new PublicationCollector(); | ||
|
||
collector.collect('publication', collections => { | ||
assert.equal(Object.keys(collections).length, 1); | ||
assert.equal(Object.keys(collections)[0], 'documents'); | ||
}); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
/* global Documents: true */ | ||
/* eslint-disable prefer-arrow-callback */ | ||
|
||
Documents = new Mongo.Collection('documents'); | ||
|
||
Meteor.publish('publication', function() { | ||
return Documents.find(); | ||
}); | ||
|
||
Meteor.publish('publicationWithUser', function() { | ||
|
||
if (!this.userId || this.userId !== 'foo') { | ||
return this.ready(); | ||
} | ||
|
||
return Documents.find(); | ||
}); | ||
|
||
Meteor.publish('publicationError', function() { | ||
|
||
if (!this.userId) { | ||
this.error(new Meteor.Error('not-authorized', 'Not authorized')); | ||
} | ||
|
||
return Documents.find(); | ||
}); |