Merge branch 'devel' into appcache

This commit is contained in:
Nick Martin
2013-03-06 17:06:05 -08:00
16 changed files with 336 additions and 75 deletions

View File

@@ -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

View File

@@ -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 <christkv@gmail.com>
----------
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 <i@izs.me> (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 <domenic@domenicdenicola.com>
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
----------

View File

@@ -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

View File

@@ -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({

View File

@@ -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;

View File

@@ -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

View File

@@ -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";
});
}
})();

View File

@@ -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:

View File

@@ -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;

View File

@@ -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;

View File

@@ -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();
});
});

View File

@@ -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;

View File

@@ -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();
});
});

View File

@@ -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) {

View File

@@ -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);

View File

@@ -19,6 +19,8 @@ Meteor.startup(function () {
Meteor.onTestsComplete && Meteor.onTestsComplete();
_resultsChanged();
Meteor.flush();
Meteor.default_connection._unsubscribeAll();
}, Session.get("groupPath"));
});