├── .github └── workflows │ └── meteor.yml ├── .gitignore ├── .gitmodules ├── .meteorignore ├── .versions ├── LICENSE ├── README.md ├── package.js ├── server.coffee ├── tests.js └── tests_prepare.js /.github/workflows/meteor.yml: -------------------------------------------------------------------------------- 1 | name: Meteor package 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-20.04 8 | 9 | container: registry.gitlab.com/tozd/docker/meteor-testing:ubuntu-focal-1.12.1 10 | 11 | steps: 12 | - uses: actions/checkout@v2 13 | with: 14 | submodules: true 15 | - run: /run-test-packages.sh 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .* 2 | .build* 3 | smart.lock 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "meteor"] 2 | path = meteor 3 | url = https://github.com/meteor/meteor.git 4 | -------------------------------------------------------------------------------- /.meteorignore: -------------------------------------------------------------------------------- 1 | meteor/* 2 | !meteor/packages 3 | meteor/packages/* 4 | !meteor/packages/minimongo 5 | -------------------------------------------------------------------------------- /.versions: -------------------------------------------------------------------------------- 1 | allow-deny@1.1.0 2 | babel-compiler@7.8.0 3 | babel-runtime@1.5.0 4 | base64@1.0.12 5 | binary-heap@1.0.11 6 | blaze@2.5.0 7 | boilerplate-generator@1.7.1 8 | caching-compiler@1.2.2 9 | callback-hook@1.4.0 10 | check@1.3.1 11 | coffeescript@2.4.1 12 | coffeescript-compiler@2.4.1 13 | ddp@1.4.0 14 | ddp-client@2.5.0 15 | ddp-common@1.4.0 16 | ddp-server@2.5.0 17 | diff-sequence@1.1.1 18 | dynamic-import@0.7.2 19 | ecmascript@0.16.1 20 | ecmascript-runtime@0.8.0 21 | ecmascript-runtime-client@0.12.1 22 | ecmascript-runtime-server@0.11.0 23 | ejson@1.1.1 24 | fetch@0.1.1 25 | geojson-utils@1.0.10 26 | htmljs@1.1.0 27 | id-map@1.1.1 28 | inter-process-messaging@0.1.1 29 | local-test:peerlibrary:reactive-mongo@0.4.1 30 | logging@1.3.1 31 | meteor@1.10.0 32 | minimongo@1.7.0 33 | modern-browsers@0.1.7 34 | modules@0.18.0 35 | modules-runtime@0.12.0 36 | mongo@1.13.0 37 | mongo-decimal@0.1.2 38 | mongo-dev-server@1.1.0 39 | mongo-id@1.0.8 40 | npm-mongo@3.9.1 41 | observe-sequence@1.0.16 42 | ordered-dict@1.1.0 43 | peerlibrary:assert@0.3.0 44 | peerlibrary:fiber-utils@0.10.0 45 | peerlibrary:reactive-mongo@0.4.1 46 | peerlibrary:server-autorun@0.8.0 47 | promise@0.12.0 48 | random@1.2.0 49 | react-fast-refresh@0.2.2 50 | reactive-var@1.0.11 51 | reload@1.3.1 52 | retry@1.1.0 53 | routepolicy@1.1.1 54 | socket-stream-client@0.4.0 55 | test-helpers@1.3.0 56 | tinytest@1.2.0 57 | tracker@1.2.0 58 | underscore@1.0.10 59 | webapp@1.13.0 60 | webapp-hashing@1.1.0 61 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, The PeerLibrary Project 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of the PeerLibrary Project nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | reactive-mongo 2 | ============== 3 | 4 | Meteor smart package which provides a fully reactive server-side MongoDB queries. This allows them to be used 5 | in [server-side autorun](https://github.com/peerlibrary/meteor-server-autorun), together with other 6 | fibers-enabled synchronous ([blocking](https://github.com/peerlibrary/meteor-blocking)) code. 7 | 8 | Adding this package to your [Meteor](http://www.meteor.com/) application will make all MongoDB queries 9 | reactive by default (you can still specify [`reactive: false`](http://docs.meteor.com/#/full/find) to 10 | queries to disable reactivity for a specific query, or use 11 | [`Tracker.nonreactive`](http://docs.meteor.com/#/full/tracker_nonreactive)). It will also automatically enable 12 | [server-side autorun](https://github.com/peerlibrary/meteor-server-autorun). All this might break some existing 13 | server-side code which might not expect to be reactive. Inspect locations where your code or packages you are using 14 | already (before using this package) call `Tracker.autorun` on the server. In most cases this occurs only in the code 15 | which is shared between client and server. 16 | 17 | Server side only. 18 | 19 | Installation 20 | ------------ 21 | 22 | ``` 23 | meteor add peerlibrary:reactive-mongo 24 | ``` 25 | 26 | Ordered vs. unordered cursors 27 | ----------------------------- 28 | 29 | In Meteor, there's a concept of *ordered* cursors. If a cursor is *ordered*, then when the order of the documents 30 | in the result set changes, the reactive computation will be invalidated and it be will re-run. 31 | 32 | By default, this package will use an ordered cursor if a `sort` option is present in the query. 33 | If no `sort` option is specified, it will use an unordered cursor. 34 | 35 | To override the default functionality, you can explicitly force or un-force the type of a cursor 36 | by passing an `ordered` option to your `find` query: 37 | 38 | ```js 39 | // Server-side code. 40 | 41 | // Will use an ordered cursor since sort option is present. 42 | Tracker.autorun(() => { 43 | Posts.find({topic: 'news'}, {sort: {name: 1}}).fetch(); 44 | }); 45 | 46 | // Will use an unordered cursor since no sort option is present. 47 | Tracker.autorun(() => { 48 | Posts.find({topic: 'news'}).fetch(); 49 | }); 50 | 51 | // Will not use an ordered cursor since it is explicitly disabled. 52 | Tracker.autorun(() => { 53 | Posts.find({topic: 'news'}, {sort: {name: 1}, ordered: false}).fetch(); 54 | }); 55 | 56 | // Will use an ordered cursor since it is explicitly enabled. 57 | Tracker.autorun(() => { 58 | Posts.find({topic: 'news'}, {ordered: true}).fetch(); 59 | }); 60 | ``` 61 | 62 | You wan to choose the cursor so that invalidations happen when and only when you want them to 63 | happen. 64 | 65 | Reactive queries and polling vs. oplog 66 | -------------------------------------- 67 | 68 | A common use case for server-side reactive computations is using a `findOne` to do a reactive join. 69 | Meteor's server-side `findOne` is a `find(selector, {limit: 1}).fetch()[0]` 70 | [under the hood](https://github.com/meteor/meteor/blob/devel/packages/mongo/mongo_driver.js#L784). 71 | [Because Meteor oplog does not support](https://galaxy-guide.meteor.com/apm-optimize-your-app-for-oplog.html#Limit-Without-Sort) 72 | `limit` without `sort`, calling `Collection.findOne(someId)` in a server-side reactive computation will 73 | default to using polling. 74 | 75 | If you would like queries inside a server-side reactive computation to use oplog, you will need to specify 76 | a sort option for your `findOne` query **and** pass `ordered: false` to use an unordered cursor: 77 | 78 | ```js 79 | // Server-side code. 80 | 81 | // Will use oplog since it has sort, limit, and is unordered. 82 | Tracker.autorun(() => { 83 | Collection.findOne(someId, {sort: {_id: 1}, ordered: false}); 84 | }); 85 | 86 | // Will use polling because a limit (from findOne) but no sort is specified. 87 | Tracker.autorun(() => { 88 | Collection.findOne(someId); 89 | }); 90 | ``` 91 | 92 | Acknowledgments 93 | --------------- 94 | 95 | This package is based on the great work by [Diggory Blake](https://github.com/Diggsey/meteor-reactive-publish) 96 | who made the first implementation. 97 | -------------------------------------------------------------------------------- /package.js: -------------------------------------------------------------------------------- 1 | Package.describe({ 2 | summary: "Reactive server-side MongoDB queries", 3 | version: '0.4.1', 4 | name: 'peerlibrary:reactive-mongo', 5 | git: 'https://github.com/peerlibrary/meteor-reactive-mongo.git' 6 | }); 7 | 8 | Package.onUse(function (api) { 9 | api.versionsFrom('METEOR@1.8.1'); 10 | 11 | // Core dependencies. 12 | api.use([ 13 | 'coffeescript@2.4.1', 14 | 'ecmascript', 15 | 'underscore', 16 | 'mongo' 17 | ], 'server'); 18 | 19 | // 3rd party dependencies. 20 | api.use([ 21 | 'peerlibrary:server-autorun@0.8.0' 22 | ], 'server'); 23 | 24 | // Package can be used without PeerDB. But if PeerDB is available, make 25 | // sure it is loaded before this package so that PeerDB adds "exists" 26 | // to the cursor before we make it reactive. 27 | api.use([ 28 | 'peerlibrary:peerdb@0.27.0' 29 | ], 'server', {'weak': true}); 30 | 31 | api.addFiles([ 32 | 'server.coffee' 33 | ], 'server'); 34 | }); 35 | 36 | Package.onTest(function (api) { 37 | api.versionsFrom('METEOR@1.8.1'); 38 | 39 | // Core dependencies. 40 | api.use([ 41 | 'ecmascript', 42 | 'tinytest', 43 | 'test-helpers', 44 | 'mongo', 45 | 'underscore', 46 | 'reactive-var', 47 | 'random', 48 | 'minimongo', 49 | 'ejson', 50 | 'mongo-id', 51 | 'id-map' 52 | ], 'server'); 53 | 54 | // 3rd party dependencies. 55 | api.use([ 56 | 'peerlibrary:server-autorun@0.8.0' 57 | ], 'server'); 58 | 59 | // Internal dependencies. 60 | api.use([ 61 | 'peerlibrary:reactive-mongo' 62 | ]); 63 | 64 | api.addFiles([ 65 | // We modify environment to make Minimongo tests in fact use MongoDB database. 66 | 'tests_prepare.js', 67 | 'meteor/packages/minimongo/matcher.js', 68 | 'meteor/packages/minimongo/minimongo_tests.js', 69 | 'meteor/packages/minimongo/minimongo_tests_client.js', 70 | 'meteor/packages/minimongo/minimongo_tests_server.js', 71 | 'tests.js' 72 | ], 'server'); 73 | }); 74 | -------------------------------------------------------------------------------- /server.coffee: -------------------------------------------------------------------------------- 1 | MeteorCursor = Object.getPrototypeOf(MongoInternals.defaultRemoteCollectionDriver().mongo.find()).constructor 2 | 3 | originalObserveChanges = MeteorCursor::observeChanges 4 | originalCount = MeteorCursor::count 5 | 6 | # This is a PeerDB extension. It might not exist if the package is used without PeerDB. 7 | # But we defined a week dependency on PeerDB so that it is loaded before this package 8 | # to that PeerDB adds this extension before we get here. 9 | originalExists = MeteorCursor::exists 10 | 11 | MeteorCursor::_isReactive = -> 12 | # By default we make all cursors reactive. But you can 13 | # still disable that, the same as on the client. 14 | @_cursorDescription.options.reactive ? true 15 | 16 | MeteorCursor::_depend = (changers) -> 17 | return unless Tracker.active 18 | 19 | dependency = new Tracker.Dependency() 20 | dependency.depend() 21 | 22 | # On server side observe does not have _suppress_initial, 23 | # so we are skipping initial documents manually. 24 | initializing = true 25 | 26 | callback = {} 27 | for fnName in ['added', 'changed', 'removed', 'addedBefore', 'movedBefore'] when changers[fnName] 28 | callback[fnName] = -> 29 | dependency.changed() unless initializing 30 | 31 | # observeChanges will stop() when this computation is invalidated. 32 | @observeChanges callback, {nonMutatingCallbacks: true} 33 | 34 | initializing = false 35 | 36 | MeteorCursor::observeChanges = (callbacks, options = {}) -> 37 | handle = originalObserveChanges.call @, callbacks, options 38 | if Tracker.active and @_isReactive() 39 | Tracker.onInvalidate => 40 | handle.stop() 41 | handle 42 | 43 | callbacksOrdered = 44 | addedBefore: true 45 | removed: true 46 | changed: true 47 | movedBefore: true 48 | 49 | callbacksUnordered = 50 | added: true 51 | changed: true 52 | removed: true 53 | 54 | for method in ['forEach', 'map', 'fetch'] 55 | do (method) -> 56 | originalMethod = MeteorCursor::[method] 57 | MeteorCursor::[method] = (args...) -> 58 | if @_isReactive() 59 | {sort, ordered} = @_cursorDescription.options 60 | if ordered? 61 | callbacks = if ordered then callbacksOrdered else callbacksUnordered 62 | else 63 | callbacks = if !!sort then callbacksOrdered else callbacksUnordered 64 | @_depend callbacks 65 | 66 | originalMethod.apply @, args 67 | 68 | MeteorCursor::count = (args...) -> 69 | if @_isReactive() 70 | @_depend 71 | added: true 72 | removed: true 73 | 74 | originalCount.apply @, args 75 | 76 | if originalExists 77 | MeteorCursor::exists = (args...) -> 78 | if @_isReactive() 79 | @_depend 80 | added: true 81 | removed: true 82 | 83 | originalExists.apply @, args 84 | -------------------------------------------------------------------------------- /tests.js: -------------------------------------------------------------------------------- 1 | // These tests are the same as those in minimongo_tests.js, just that after every batch of database modify queries 2 | // we sleep a bit to allow for changes to propagate back to Meteor and run any observes and invalidate any autoruns. 3 | 4 | Tinytest.add("reactive-mongo - reactive stop", function (test) { 5 | var coll = new LocalCollection(); 6 | coll.insert({_id: 'A'}); 7 | coll.insert({_id: 'B'}); 8 | coll.insert({_id: 'C'}); 9 | Meteor._sleepForMs(100); 10 | 11 | var addBefore = function (str, newChar, before) { 12 | var idx = str.indexOf(before); 13 | if (idx === -1) 14 | return str + newChar; 15 | return str.slice(0, idx) + newChar + str.slice(idx); 16 | }; 17 | 18 | var x, y; 19 | var sortOrder = ReactiveVar(1); 20 | 21 | var c = Tracker.autorun(function () { 22 | var q = coll.find({}, {sort: {_id: sortOrder.get()}}); 23 | x = ""; 24 | q.observe({ addedAt: function (doc, atIndex, before) { 25 | x = addBefore(x, doc._id, before); 26 | }}); 27 | y = ""; 28 | q.observeChanges({ addedBefore: function (id, fields, before) { 29 | y = addBefore(y, id, before); 30 | }}); 31 | }); 32 | 33 | test.equal(x, "ABC"); 34 | test.equal(y, "ABC"); 35 | 36 | sortOrder.set(-1); 37 | test.equal(x, "ABC"); 38 | test.equal(y, "ABC"); 39 | Tracker.flush(); 40 | test.equal(x, "CBA"); 41 | test.equal(y, "CBA"); 42 | 43 | coll.insert({_id: 'D'}); 44 | coll.insert({_id: 'E'}); 45 | Meteor._sleepForMs(100); 46 | test.equal(x, "EDCBA"); 47 | test.equal(y, "EDCBA"); 48 | 49 | c.stop(); 50 | // stopping kills the observes immediately 51 | coll.insert({_id: 'F'}); 52 | Meteor._sleepForMs(100); 53 | test.equal(x, "EDCBA"); 54 | test.equal(y, "EDCBA"); 55 | }); 56 | 57 | Tinytest.add("reactive-mongo - fetch in observe", function (test) { 58 | var coll = new LocalCollection; 59 | var callbackInvoked = false; 60 | var observe = coll.find().observeChanges({ 61 | added: function (id, fields) { 62 | callbackInvoked = true; 63 | test.equal(fields, {foo: 1}); 64 | var doc = coll.findOne({foo: 1}); 65 | test.isTrue(doc); 66 | test.equal(doc.foo, 1); 67 | } 68 | }); 69 | test.isFalse(callbackInvoked); 70 | var computation = Tracker.autorun(function (computation) { 71 | if (computation.firstRun) { 72 | coll.insert({foo: 1}); 73 | Meteor._sleepForMs(100); 74 | } 75 | }); 76 | test.isTrue(callbackInvoked); 77 | observe.stop(); 78 | computation.stop(); 79 | }); 80 | 81 | Tinytest.add("reactive-mongo - count on cursor with limit", function(test){ 82 | var coll = new LocalCollection(), count; 83 | 84 | coll.insert({_id: 'A'}); 85 | coll.insert({_id: 'B'}); 86 | coll.insert({_id: 'C'}); 87 | coll.insert({_id: 'D'}); 88 | Meteor._sleepForMs(100); 89 | 90 | var c = Tracker.autorun(function (c) { 91 | var cursor = coll.find({_id: {$exists: true}}, {sort: {_id: 1}, limit: 3}); 92 | count = cursor.count(); 93 | }); 94 | 95 | test.equal(count, 3); 96 | 97 | coll.remove('A'); // still 3 in the collection 98 | Meteor._sleepForMs(100); 99 | Tracker.flush(); 100 | test.equal(count, 3); 101 | 102 | coll.remove('B'); // expect count now 2 103 | Meteor._sleepForMs(100); 104 | Tracker.flush(); 105 | test.equal(count, 2); 106 | 107 | 108 | coll.insert({_id: 'A'}); // now 3 again 109 | Meteor._sleepForMs(100); 110 | Tracker.flush(); 111 | test.equal(count, 3); 112 | 113 | coll.insert({_id: 'B'}); // now 4 entries, but count should be 3 still 114 | Meteor._sleepForMs(100); 115 | Tracker.flush(); 116 | test.equal(count, 3); 117 | 118 | c.stop(); 119 | }); 120 | 121 | Tinytest.add("reactive-mongo - fine-grained reactivity of query with fields projection", function (test) { 122 | var X = new LocalCollection; 123 | var id = "asdf"; 124 | X.insert({_id: id, foo: {bar: 123}}); 125 | 126 | var callbackInvoked = false; 127 | var computation = Tracker.autorun(function () { 128 | callbackInvoked = true; 129 | return X.findOne(id, { fields: { 'foo.bar': 1 } }); 130 | }); 131 | test.isTrue(callbackInvoked); 132 | callbackInvoked = false; 133 | X.update(id, {$set: {'foo.baz': 456}}); 134 | Meteor._sleepForMs(100); 135 | test.isFalse(callbackInvoked); 136 | X.update(id, {$set: {'foo.bar': 124}}); 137 | Meteor._sleepForMs(100); 138 | Tracker.flush(); 139 | test.isTrue(callbackInvoked); 140 | 141 | computation.stop(); 142 | }); 143 | -------------------------------------------------------------------------------- /tests_prepare.js: -------------------------------------------------------------------------------- 1 | // We are repurposing Minimongo tests to test reactive server-side MongoDB 2 | // collections. So we have to do some preparations here. 3 | 4 | originalLocalCollection = LocalCollection; 5 | LocalCollection = function (name, options) { 6 | // We want always to use MongoDB, not local collections. 7 | name = name || Random.id(); 8 | // To make Mongo.Collection generate ObjectIDs by default. 9 | options = options || {}; 10 | options.idGeneration = 'MONGO'; 11 | return new Mongo.Collection(name, options); 12 | }; 13 | 14 | LocalCollection.wrapTransform = originalLocalCollection.wrapTransform; 15 | LocalCollection._f = originalLocalCollection._f; 16 | LocalCollection._compileProjection = originalLocalCollection._compileProjection; 17 | LocalCollection._binarySearch = originalLocalCollection._binarySearch; 18 | 19 | // Currently server and client side behaves differently when counting with skip. So we make it 20 | // behave the same for tests. See https://github.com/meteor/meteor/issues/1201 21 | var SynchronousCursor = Object.getPrototypeOf(MongoInternals.defaultRemoteCollectionDriver().mongo._createSynchronousCursor({'collectionName': 'foobar', 'options': {}})).constructor; 22 | SynchronousCursor.prototype.count = function () { 23 | return this._synchronousCount({'applySkipLimit': true}).wait(); 24 | }; 25 | 26 | var originalFind = Mongo.Collection.prototype.find; 27 | Mongo.Collection.prototype.find = function (selector, options) { 28 | // Few tests are expecting an exception for unsupported operators. 29 | if (options && options.fields && (options.fields.grades || options.fields['grades.$'])) { 30 | throw new Error("Unsupported in Minimongo"); 31 | } 32 | if (selector && selector.location && selector.location['$not']) { 33 | throw new Error("Unsupported in Minimongo"); 34 | } 35 | if (selector && selector['$and'] && selector['$and'][0].location) { 36 | throw new Error("Unsupported in Minimongo"); 37 | } 38 | if (selector && selector['$or'] && selector['$or'][0].location) { 39 | throw new Error("Unsupported in Minimongo"); 40 | } 41 | if (selector && selector['$nor'] && selector['$nor'][0].location) { 42 | throw new Error("Unsupported in Minimongo"); 43 | } 44 | if (selector && selector['$and'] && selector['$and'][0]['$and'] && selector['$and'][0]['$and'][0].location) { 45 | throw new Error("Unsupported in Minimongo"); 46 | } 47 | 48 | // Geo queries need indexes. 49 | if (selector && selector['rest.loc']) { 50 | this._ensureIndex({'rest.loc': '2d'}); 51 | } 52 | if (selector && selector['location']) { 53 | this._ensureIndex({'location': '2dsphere'}); 54 | } 55 | if (selector && selector['a.b']) { 56 | this._ensureIndex({'a.b': '2d'}); 57 | } 58 | 59 | return originalFind.apply(this, arguments); 60 | }; 61 | 62 | var originalUpdate = Mongo.Collection.prototype.update; 63 | Mongo.Collection.prototype.update = function (selector, mod, options, callback) { 64 | if (selector && selector['a.b'] && selector['a.b'].$near) { 65 | this._ensureIndex({'a.b': '2d'}); 66 | } 67 | if (selector && selector['a.c'] && selector['a.c'].$near) { 68 | this._ensureIndex({'a.c': '2d'}); 69 | } 70 | 71 | return originalUpdate.apply(this, arguments); 72 | }; 73 | 74 | var IGNORED_TESTS = [ 75 | // Tests which do not test any reactive behavior, just Minimongo specifics, 76 | // and use code which does not exist on the server. 77 | 'minimongo - saveOriginals', 78 | 'minimongo - saveOriginals errors', 79 | 'minimongo - pause', 80 | 'minimongo - ids matched by selector', 81 | 82 | // Flaky. Failing on Travis CI. 83 | 'minimongo - reactive count with cached cursor', 84 | 85 | // Fail because of difference between Minimongo and server. For some of these 86 | // tests (those with autorun) we have a fixed version in tests.js. 87 | 'minimongo - sort function', 88 | 'minimongo - cannot $set with null bytes', 89 | 'minimongo - cannot insert using invalid field names', 90 | 'minimongo - $near operator tests', 91 | 'minimongo - $near and $geometry for legacy coordinates', 92 | 'minimongo - modify', 93 | // See: https://github.com/meteor/meteor-feature-requests/issues/252 94 | 'minimongo - basics', 95 | // See: https://github.com/meteor/meteor/issues/3597 96 | 'minimongo - observe ordered', 97 | 'minimongo - observe ordered: true', 98 | 'minimongo - observe ordered: false', 99 | 'minimongo - observe ordered with projection', 100 | 'minimongo - reactive stop', 101 | 'minimongo - fetch in observe', 102 | 'minimongo - count on cursor with limit', 103 | 'minimongo - reactive skip/limit count while updating', 104 | 'minimongo - fine-grained reactivity of query with fields projection', 105 | // See: https://github.com/meteor/meteor/issues/11855 106 | 'minimongo - fetch with projection, subarrays', 107 | ]; 108 | 109 | var originalTinytestAdd = Tinytest.add; 110 | Tinytest.add = function (name, func) { 111 | if (_.contains(IGNORED_TESTS, name)) return; 112 | return originalTinytestAdd.call(Tinytest, name, func); 113 | }; 114 | --------------------------------------------------------------------------------