diff --git a/History.md b/History.md index 832904c823..a2e7327082 100644 --- a/History.md +++ b/History.md @@ -1,18 +1,44 @@ ## vNEXT +* Publish functions may now return an array of cursors to publish. Currently, + the cursors must all be from different collections. #716 + * User documents have id's when onCreateUser and validateNewUser hooks run. -* Removed all restrictions on EJSON types in MongoDB, even user-defined ones. +* Encode and store custom EJSON types in MongoDB. -* `coffeescript` package: Support literate Coffeescript files with the extension - `.litcoffee`. +* Support literate CoffeeScript files with the extension `.litcoffee`. #766 -* Fixed bug where an empty `fields` object was sometimes passed to a `changed` - callback of `Cursor.observeChanges`. +* If you call `observe` or `observeChanges` on a cursor created with the + `reactive: false` option, it now only calls initial add callbacks and + does not continue watching the query. #771 + +* In an event handler, if the data context is falsey, default it to `{}` + rather than to the global object. #777 + +* Revert caching header change from 0.5.5. This fixes image flicker on redraw. + +* Stop making `Session` available on the server; it's not useful there. #751 + +* Force URLs in stack traces in browser consoles to be hyperlinks. #725 + +* Suppress spurious `changed` callbacks with empty `fields` from + `Cursor.observeChanges`. + +* Fix logic bug in template branch matching. #724 + +* Make `spiderable` user-agent test case insensitive. #721 + +* Fix several bugs in EJSON type support: + * Fix `{$type: 5}` selectors for binary values on browsers that do + not support `Uint8Array`. + * Fix EJSON equality on falsey values. + * Fix for returning a scalar EJSON type from a method. #731 + +Patches contributed by GitHub users awwx, cmather, graemian, jmhredsox, +kevinxucs, krizka, mitar, raix, and rasmuserik. -* Fixed `{$type: 5}` selectors for binary values on browsers that do not support - `Uint8Array` ## v0.5.7 diff --git a/LICENSE.txt b/LICENSE.txt index bd83550eb8..2a6fbc3e05 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -286,30 +286,13 @@ All rights reserved. ---------- optimist: https://github.com/substack/node-optimist ----------- - -Copyright 2010 James Halliday (mail@substack.net) - - ----------- node-mkdirp: https://github.com/substack/node-mkdirp ----------- - -Copyright 2010 James Halliday (mail@substack.net) - - ----------- wordwrap: https://github.com/substack/node-wordwrap ----------- - -Copyright 2011 James Halliday (mail@substack.net) - - ----------- node-archy: https://github.com/substack/node-archy +node-shell-quote: https://github.com/substack/node-shell-quote ---------- -Copyright 2012 James Halliday (mail@substack.net) +Copyright 2010, 2011, 2012 James Halliday (mail@substack.net) ---------- @@ -356,6 +339,14 @@ node-form-data: https://github.com/felixge/node-form-data Copyright (c) 2012 Felix Geisendörfer (felix@debuggable.com) and contributors +---------- +node-kexec: https://github.com/jprichardson/node-kexec +---------- + +Copyright (c) 2011-2012 JP Richardson + + + ============== Apache License ============== @@ -422,6 +413,20 @@ bson: https://github.com/mongodb/js-bson Copyright (c) 2012 Christian Amor Kvalheim +---------- +websocket: https://github.com/Worlize/WebSocket-Node/blob/master/LICENSE +---------- + +Brian McKelvey + + +---------- +amdefine: https://github.com/jrburke/amdefine +---------- + +Copyright (c) 2011, The Dojo Foundation + + ============ BSD Licenses @@ -579,11 +584,13 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND node-tar: https://github.com/isaacs/node-tar fstream: https://github.com/isaacs/fstream fstream-npm: https://github.com/isaacs/fstream-npm +fstream-ignore: https://github.com/isaacs/fstream-ignore read: https://github.com/isaacs/read block-stream: https://github.com/isaacs/block-stream node-glob: https://github.com/isaacs/node-glob chownr: https://github.com/isaacs/chownr uid-number: https://github.com/isaacs/uid-number +sigmund: https://github.com/isaacs/sigmund ---------- Copyright (c) Isaac Z. Schlueter @@ -657,7 +664,7 @@ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------- -d3: https://github.com/mbostock/d3 +D3: https://github.com/mbostock/d3 ---------- Copyright (c) 2012, Michael Bostock @@ -756,10 +763,40 @@ OF THE POSSIBILITY OF SUCH DAMAGE. ---------- -D3: http://d3js.org/ +ycssmin: https://github.com/yui/ycssmin ---------- -Copyright (c) 2012, Michael Bostock +Copyright 2012 Yahoo! Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Yahoo! Inc. nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL YAHOO! INC. BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +---------- +source-map: https://github.com/mozilla/source-map +---------- + +Copyright (c) 2009-2011, Mozilla Foundation and contributors All rights reserved. Redistribution and use in source and binary forms, with or without @@ -772,19 +809,20 @@ modification, are permitted provided that the following conditions are met: this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -* The name Michael Bostock may not be used to endorse or promote products - derived from this software without specific prior written permission. +* Neither the names of the Mozilla Foundation nor the names of project + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY -OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. @@ -834,6 +872,7 @@ mimelib: https://github.com/andris9/mimelib mailcomposer: https://github.com/andris9/mailcomposer simplesmtp: https://github.com/andris9/simplesmtp rai: https://github.com/andris9/rai +xoauth2: https://github.com/andris9/xoauth2 ---------- Copyright (c) 2012 Andris Reinman @@ -876,6 +915,26 @@ By Isaac Z. Schlueter (http://blog.izs.me/) 0. You just DO WHAT THE FUCK YOU WANT TO. +---------- +opener: https://github.com/domenic/opener +---------- + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Copyright (C) 2012 Domenic Denicola + + Everyone is permitted to copy and distribute verbatim or modified + copies of this license document, and changing it is allowed as long + as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. + + + ---------- node-stream-buffer: https://github.com/samcday/node-stream-buffer ---------- diff --git a/docs/client/api.html b/docs/client/api.html index 683c8dbb11..72111483e5 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -45,7 +45,15 @@ that Meteor will call each time a client subscribes to the name. Publish functions can return a [`Collection.Cursor`](#meteor_collection_cursor), in which case Meteor -will publish that cursor's documents. +will publish that cursor's documents. You can also return an array of +`Collection.Cursor`s, in which case Meteor will publish all of the +cursors. + +{{#warning}} +If you return multiple cursors in an array, they currently must all be from +different collections. We hope to lift this restriction in a future release. +cursors. +{{/warning}} // server: publish the rooms collection, minus secret info. Meteor.publish("rooms", function () { @@ -59,6 +67,14 @@ will publish that cursor's documents. return Rooms.find({admin: this.userId}, {fields: {secretInfo: 1}}); }); + // publish dependent documents and simulate joins + Meteor.publish("roomAndMessages", function (roomId) { + return [ + Rooms.find({_id: roomId}, {fields: {secretInfo: 0}}), + Messages.find({roomId: roomId}) + ]; + }); + Otherwise, the publish function should call the functions [`added`](#publish_added) (when a new document is added to the published record set), [`changed`](#publish_changed) (when some fields on a document in the @@ -136,7 +152,7 @@ function is rerun with the new value. {{> api_box subscription_onStop}} -If you call [`observe`](#observe) or [`observeChanges`](#observeChanges) in your +If you call [`observe`](#observe) or [`observeChanges`](#observe_changes) in your publish handler, this is the place to stop the observes. {{> api_box subscription_error}} @@ -900,7 +916,7 @@ Establishes a *live query* that invokes callbacks when the result of the query changes. The callbacks receive the entire contents of the document that was affected, as well as its old contents, if applicable. If you only need to receive the fields that changed, see -[`observeChanges`](#cursor_observe_changes). +[`observeChanges`](#observe_changes). `callbacks` may have the following functions as properties: @@ -952,12 +968,15 @@ or more times to deliver the initial results of the query. `observe` returns a live query handle, which is an object with a `stop` method. Call `stop` with no arguments to stop calling the callback functions and tear down the query. **The query will run forever until you call this.** +(If the cursor was created with the option `reactive` set to false, it will +only deliver the initial results and will not call any further callbacks; +it is not necessary to call `stop` on the handle.) {{> api_box cursor_observe_changes}} Establishes a *live query* that invokes callbacks when the result of -the query changes. In contrast to [`observe`](#cursor_observe), +the query changes. In contrast to [`observe`](#observe), `observeChanges` provides only the difference between the old and new result set, not the entire contents of the document that changed. @@ -1002,6 +1021,9 @@ zero or more times to deliver the initial results of the query. `observeChanges` returns a live query handle, which is an object with a `stop` method. Call `stop` with no arguments to stop calling the callback functions and tear down the query. **The query will run forever until you call this.** +(If the cursor was created with the option `reactive` set to false, it will +only deliver the initial results and will not call any further callbacks; +it is not necessary to call `stop` on the handle.) {{#note}} Unlike `observe`, `observeChanges` does not provide absolute position diff --git a/docs/client/docs.js b/docs/client/docs.js index 4785064814..fda2a149bf 100644 --- a/docs/client/docs.js +++ b/docs/client/docs.js @@ -53,6 +53,9 @@ Meteor.startup(function () { }; var scrollToSection = function (section) { + if (! $(section).length) + return; + ignore_waypoints = true; Session.set("section", section.substr(1)); scroller().animate({ diff --git a/packages/livedata/livedata_connection.js b/packages/livedata/livedata_connection.js index 7bf2cfebe1..61971de604 100644 --- a/packages/livedata/livedata_connection.js +++ b/packages/livedata/livedata_connection.js @@ -764,6 +764,16 @@ _.extend(Meteor._LivedataConnection.prototype, { } }, + // This is very much a private function we use to make the tests + // take up fewer server resources after they complete. + _unsubscribeAll: function () { + var self = this; + _.each(_.clone(self._subscriptions), function (sub, id) { + self._send({msg: 'unsub', id: id}); + delete self._subscriptions[id]; + }); + }, + // Sends the DDP stringification of the given message object _send: function (obj) { var self = this; diff --git a/packages/livedata/livedata_server.js b/packages/livedata/livedata_server.js index 7fd44c950f..901dac065a 100644 --- a/packages/livedata/livedata_server.js +++ b/packages/livedata/livedata_server.js @@ -821,8 +821,9 @@ _.extend(Meteor._LivedataSubscription.prototype, { // SPECIAL CASE: Instead of writing their own callbacks that invoke // this.added/changed/ready/etc, the user can just return a collection - // cursor from the publish function; we call its _publishCursor method which - // starts observing the cursor and publishes the results. + // cursor or array of cursors from the publish function; we call their + // _publishCursor method which starts observing the cursor and publishes the + // results. Note that _publishCursor does NOT call ready(). // // XXX This uses an undocumented interface which only the Mongo cursor // interface publishes. Should we make this interface public and encourage @@ -834,8 +835,40 @@ _.extend(Meteor._LivedataSubscription.prototype, { // reactiveThingy.publishMe(); // }); // }; - if (res && res._publishCursor) + var isCursor = function (c) { + return c && c._publishCursor; + }; + if (isCursor(res)) { res._publishCursor(self); + // _publishCursor only returns after the initial added callbacks have run. + // mark subscription as ready. + self.ready(); + } else if (_.isArray(res)) { + // check all the elements are cursors + if (! _.all(res, isCursor)) { + self.error(new Error("Publish function returned an array of non-Cursors")); + return; + } + // find duplicate collection names + // XXX we should support overlapping cursors, but that would require the + // merge box to allow overlap within a subscription + var collectionNames = {}; + for (var i = 0; i < res.length; ++i) { + var collectionName = res[i]._getCollectionName(); + if (_.has(collectionNames, collectionName)) { + self.error(new Error( + "Publish function returned multiple cursors for collection " + + collectionName)); + return; + } + collectionNames[collectionName] = true; + }; + + _.each(res, function (cur) { + cur._publishCursor(self); + }); + self.ready(); + } }, // This calls all stop callbacks and prevents the handler from updating any diff --git a/packages/livedata/livedata_test_service.js b/packages/livedata/livedata_test_service.js index 8b6e33241f..213282a92e 100644 --- a/packages/livedata/livedata_test_service.js +++ b/packages/livedata/livedata_test_service.js @@ -277,4 +277,47 @@ if (Meteor.isServer) { } +/*****/ + +/// Helpers for "livedata - publish multiple cursors" +One = new Meteor.Collection("collectionOne"); +Two = new Meteor.Collection("collectionTwo"); + +if (Meteor.isServer) { + One.remove({}); + One.insert({name: "value1"}); + One.insert({name: "value2"}); + + Two.remove({}); + Two.insert({name: "value3"}); + Two.insert({name: "value4"}); + Two.insert({name: "value5"}); + + Meteor.publish("multiPublish", function (options) { + if (options.normal) { + return [ + One.find(), + Two.find() + ]; + } else if (options.dup) { + // Suppress the log of the expected internal error. + Meteor._suppress_log(1); + return [ + One.find(), + One.find({name: "value2"}), // multiple cursors for one collection - error + Two.find() + ]; + } else if (options.notCursor) { + // Suppress the log of the expected internal error. + Meteor._suppress_log(1); + return [ + One.find(), + "not a cursor", + Two.find() + ]; + } else + throw "unexpected options"; + }); +} + })(); diff --git a/packages/livedata/livedata_tests.js b/packages/livedata/livedata_tests.js index ef590e0415..02a8f9a6a4 100644 --- a/packages/livedata/livedata_tests.js +++ b/packages/livedata/livedata_tests.js @@ -507,6 +507,30 @@ if (Meteor.isClient) { conn._stream.forceDisconnect(); } ];})()); + + testAsyncMulti("livedata - publish multiple cursors", [ + function (test, expect) { + Meteor.subscribe("multiPublish", {normal: 1}, { + onReady: expect(function () { + test.equal(One.find().count(), 2); + test.equal(Two.find().count(), 3); + }), + onError: failure() + }); + }, + function (test, expect) { + Meteor.subscribe("multiPublish", {dup: 1}, { + onReady: failure(), + onError: expect(failure(test, 500, "Internal server error")) + }); + }, + function (test, expect) { + Meteor.subscribe("multiPublish", {notCursor: 1}, { + onReady: failure(), + onError: expect(failure(test, 500, "Internal server error")) + }); + } + ]); } // XXX some things to test in greater detail: diff --git a/packages/meteor/fiber_helpers.js b/packages/meteor/fiber_helpers.js index 07d4d40cdf..cee9407694 100644 --- a/packages/meteor/fiber_helpers.js +++ b/packages/meteor/fiber_helpers.js @@ -60,10 +60,12 @@ _.extend(Meteor._SynchronousQueue.prototype, { runTask: function (task) { var self = this; - if (!Fiber.current) - throw new Error("Can only call runTask in a Fiber"); - if (self._currentTaskFiber === Fiber.current) - throw new Error("Can't runTask from another task in the same fiber"); + if (!self.safeToRunTask()) { + if (Fiber.current) + throw new Error("Can't runTask from another task in the same fiber"); + else + throw new Error("Can only call runTask in a Fiber"); + } var fut = new Future; self._taskHandles.push({task: task, future: fut}); @@ -82,6 +84,12 @@ _.extend(Meteor._SynchronousQueue.prototype, { var self = this; return self._taskRunning; }, + + safeToRunTask: function () { + var self = this; + return Fiber.current && self._currentTaskFiber !== Fiber.current; + }, + _scheduleRun: function () { var self = this; diff --git a/packages/minimongo/minimongo.js b/packages/minimongo/minimongo.js index 30bd53b08c..c75646ede8 100644 --- a/packages/minimongo/minimongo.js +++ b/packages/minimongo/minimongo.js @@ -224,10 +224,8 @@ _.extend(LocalCollection.Cursor.prototype, { if (!ordered && (self.skip || self.limit)) throw new Error("must use ordered observe with skip or limit"); - var qid = self.collection.next_qid++; - // XXX merge this object w/ "this" Cursor. they're the same. - var query = self.collection.queries[qid] = { + var query = { selector_f: self.selector_f, // not fast pathed sort_f: ordered && self.sort_f, results_snapshot: null, @@ -235,6 +233,14 @@ _.extend(LocalCollection.Cursor.prototype, { cursor: this, observeChanges: options.observeChanges }; + var qid; + + // Non-reactive queries call added[Before] and then never call anything + // else. + if (self.reactive) { + qid = self.collection.next_qid++; + self.collection.queries[qid] = query; + } query.results = self._getRawObjects(ordered); if (self.collection.paused) query.results_snapshot = (ordered ? [] : {}); @@ -273,7 +279,8 @@ _.extend(LocalCollection.Cursor.prototype, { _.extend(handle, { collection: self.collection, stop: function () { - delete self.collection.queries[qid]; + if (self.reactive) + delete self.collection.queries[qid]; } }); return handle; diff --git a/packages/minimongo/minimongo_tests.js b/packages/minimongo/minimongo_tests.js index 272836676f..3131382bb2 100644 --- a/packages/minimongo/minimongo_tests.js +++ b/packages/minimongo/minimongo_tests.js @@ -1411,6 +1411,14 @@ _.each([true, false], function (ordered) { c.update(4, {$set: {eek: 5}}); expect('cb4_'); handle.stop(); + + // Test observe with reactive: false. + handle = c.find({tags: "flower"}, {reactive: false}).observe(makecb('c')); + expect('ac4_ac5_'); + // This insert shouldn't trigger a callback because it's not reactive. + c.insert({_id: 6, name: "river", tags: ["flower"]}); + expect(''); + handle.stop(); }); }); diff --git a/packages/mongo-livedata/mongo_driver.js b/packages/mongo-livedata/mongo_driver.js index d317737f54..0c0f6782c7 100644 --- a/packages/mongo-livedata/mongo_driver.js +++ b/packages/mongo-livedata/mongo_driver.js @@ -398,14 +398,21 @@ Cursor.prototype._publishCursor = function (sub) { } }); - // observeChanges only returns after the initial added callbacks have run. - // mark subscription as ready. - sub.ready(); + // We don't call sub.ready() here: it gets called in livedata_server, after + // possibly calling _publishCursor on multiple returned cursors. // register stop callback (expects lambda w/ no args). sub.onStop(function () {observeHandle.stop();}); }; +// Used to guarantee that publish functions return at most one cursor per +// collection. Private, because we might later have cursors that include +// documents from multiple collections somehow. +Cursor.prototype._getCollectionName = function () { + var self = this; + return self._cursorDescription.collectionName; +} + Cursor.prototype.observe = function (callbacks) { var self = this; return LocalCollection._observeFromObserveChanges(self, callbacks); @@ -815,6 +822,14 @@ _.extend(LiveResultsSet.prototype, { // with a call to _pollMongo or another call to this function. _addObserveHandleAndSendInitialAdds: function (handle) { var self = this; + + // Check this before calling runTask (even though runTask does the same + // check) so that we don't leak a LiveResultsSet by incrementing + // _addHandleTasksScheduledButNotPerformed and never decrementing it. + if (!self._taskQueue.safeToRunTask()) + throw new Error( + "Can't call observe() from an observe callback on the same query"); + // Keep track of how many of these tasks are on the queue, so that // _removeObserveHandle knows if it's safe to GC. ++self._addHandleTasksScheduledButNotPerformed; diff --git a/packages/mongo-livedata/observe_changes_tests.js b/packages/mongo-livedata/observe_changes_tests.js index 366cf63554..61fb4daf92 100644 --- a/packages/mongo-livedata/observe_changes_tests.js +++ b/packages/mongo-livedata/observe_changes_tests.js @@ -27,7 +27,7 @@ _.each ([{added:'added', forceOrdered: true}, function (logger) { var barid = c.insert({thing: "stuff"}); var fooid = c.insert({noodles: "good", bacon: "bad", apples: "ok"}); - c.find(fooid).observeChanges(logger); + var handle = c.find(fooid).observeChanges(logger); if (added === 'added') logger.expectResult(added, [fooid, {noodles: "good", bacon: "bad",apples: "ok"}]); else @@ -44,6 +44,7 @@ _.each ([{added:'added', forceOrdered: true}, c.insert({noodles: "good", bacon: "bad", apples: "ok"}); logger.expectNoResult(); + handle.stop(); onComplete(); }); }); @@ -54,9 +55,10 @@ Tinytest.addAsync("observeChanges - single id - initial adds", function (test, o var c = makeCollection(); withCallbackLogger(test, ["added", "changed", "removed"], Meteor.isServer, function (logger) { var fooid = c.insert({noodles: "good", bacon: "bad", apples: "ok"}); - c.find(fooid).observeChanges(logger); + var handle = c.find(fooid).observeChanges(logger); logger.expectResult("added", [fooid, {noodles: "good", bacon: "bad", apples: "ok"}]); logger.expectNoResult(); + handle.stop(); onComplete(); }); }); @@ -68,7 +70,7 @@ Tinytest.addAsync("observeChanges - unordered - initial adds", function (test, o withCallbackLogger(test, ["added", "changed", "removed"], Meteor.isServer, function (logger) { var fooid = c.insert({noodles: "good", bacon: "bad", apples: "ok"}); var barid = c.insert({noodles: "good", bacon: "weird", apples: "ok"}); - c.find().observeChanges(logger); + var handle = c.find().observeChanges(logger); logger.expectResultUnordered([ {callback: "added", args: [fooid, {noodles: "good", bacon: "bad", apples: "ok"}]}, @@ -76,6 +78,7 @@ Tinytest.addAsync("observeChanges - unordered - initial adds", function (test, o args: [barid, {noodles: "good", bacon: "weird", apples: "ok"}]} ]); logger.expectNoResult(); + handle.stop(); onComplete(); }); }); @@ -83,7 +86,7 @@ Tinytest.addAsync("observeChanges - unordered - initial adds", function (test, o Tinytest.addAsync("observeChanges - unordered - basics", function (test, onComplete) { var c = makeCollection(); withCallbackLogger(test, ["added", "changed", "removed"], Meteor.isServer, function (logger) { - c.find().observeChanges(logger); + var handle = c.find().observeChanges(logger); var barid = c.insert({thing: "stuff"}); logger.expectResultOnly("added", [barid, {thing: "stuff"}]); @@ -104,6 +107,7 @@ Tinytest.addAsync("observeChanges - unordered - basics", function (test, onCompl logger.expectResult("added", [fooid, {noodles: "good", bacon: "bad", apples: "ok"}]); logger.expectNoResult(); + handle.stop(); onComplete(); }); }); @@ -112,7 +116,7 @@ if (Meteor.isServer) { Tinytest.addAsync("observeChanges - unordered - specific fields", function (test, onComplete) { var c = makeCollection(); withCallbackLogger(test, ["added", "changed", "removed"], Meteor.isServer, function (logger) { - c.find({}, {fields:{noodles: 1, bacon: 1}}).observeChanges(logger); + var handle = c.find({}, {fields:{noodles: 1, bacon: 1}}).observeChanges(logger); var barid = c.insert({thing: "stuff"}); logger.expectResultOnly("added", [barid, {}]); @@ -133,6 +137,7 @@ if (Meteor.isServer) { logger.expectResult("added", [fooid, {noodles: "good", bacon: "bad"}]); logger.expectNoResult(); + handle.stop(); onComplete(); }); }); @@ -142,7 +147,7 @@ if (Meteor.isServer) { Tinytest.addAsync("observeChanges - unordered - enters and exits result set through change", function (test, onComplete) { var c = makeCollection(); withCallbackLogger(test, ["added", "changed", "removed"], Meteor.isServer, function (logger) { - c.find({noodles: "good"}).observeChanges(logger); + var handle = c.find({noodles: "good"}).observeChanges(logger); var barid = c.insert({thing: "stuff"}); var fooid = c.insert({noodles: "good", bacon: "bad", apples: "ok"}); @@ -158,6 +163,7 @@ Tinytest.addAsync("observeChanges - unordered - enters and exits result set thro c.update(fooid, {noodles: "good", potatoes: "tasty", apples: "ok"}); logger.expectResult("added", [fooid, {noodles: "good", potatoes: "tasty", apples: "ok"}]); logger.expectNoResult(); + handle.stop(); onComplete(); }); }); diff --git a/packages/session/package.js b/packages/session/package.js index e6dd2d0895..2b2ca13bd9 100644 --- a/packages/session/package.js +++ b/packages/session/package.js @@ -8,19 +8,14 @@ Package.describe({ // XXX hack -- need a way to use a package at bundle time var _ = require(path.join('..', '..', 'packages', 'underscore', 'underscore.js')); -Package.on_use(function (api, where) { - where = where || ['client', 'server']; - - api.use(['underscore', 'deps'], where); +Package.on_use(function (api) { + api.use(['underscore', 'deps'], 'client'); // XXX what I really want to do is ensure that if 'reload' is going to // be loaded, it should be loaded before 'session'. Session can work // with or without reload. - if (where === "client" || - (where instanceof Array && _.indexOf(where, "client") !== -1)) { - api.use("reload", "client"); - } + api.use('reload', 'client'); - api.add_files('session.js', where); + api.add_files('session.js', 'client'); }); Package.on_test(function (api) { diff --git a/packages/spark/spark.js b/packages/spark/spark.js index 48533e30fc..9736565100 100644 --- a/packages/spark/spark.js +++ b/packages/spark/spark.js @@ -801,7 +801,7 @@ Spark.attachEvents = withRenderer(function (eventMap, html, _renderer) { } // Found a matching handler. Call it. - var eventData = Spark.getDataContext(event.currentTarget); + var eventData = Spark.getDataContext(event.currentTarget) || {}; var landmarkRange = findParentOfType(Spark._ANNOTATION_LANDMARK, range); var landmark = (landmarkRange && landmarkRange.landmark); diff --git a/packages/test-in-browser/driver.js b/packages/test-in-browser/driver.js index 2a1e7f43c1..9a55127c9a 100644 --- a/packages/test-in-browser/driver.js +++ b/packages/test-in-browser/driver.js @@ -19,6 +19,8 @@ Meteor.startup(function () { Meteor.onTestsComplete && Meteor.onTestsComplete(); _resultsChanged(); Meteor.flush(); + + Meteor.default_connection._unsubscribeAll(); }, Session.get("groupPath")); });