diff --git a/docs/guide.pug b/docs/guide.pug
index 389be5cb92f..f80b4770314 100644
--- a/docs/guide.pug
+++ b/docs/guide.pug
@@ -81,8 +81,10 @@ block content
Keys may also be assigned nested objects containing further key/type definitions
like the `meta` property above. This will happen whenever a key's value is a POJO
- that lacks a bona-fide `type` property. In these cases, only the leaves in a tree
- are given actual paths in the schema (like `meta.votes` and `meta.favs` above),
+ that doesn't have a `type` property.
+
+ In these cases, Mongoose only creates actual schema paths for leaves
+ in the tree. (like `meta.votes` and `meta.favs` above),
and the branches do not have actual paths. A side-effect of this is that `meta`
above cannot have its own validation. If validation is needed up the tree, a path
needs to be created up the tree - see the [Subdocuments](./subdocs.html) section
diff --git a/docs/index.pug b/docs/index.pug
index 656c4db626f..c2b161fe421 100644
--- a/docs/index.pug
+++ b/docs/index.pug
@@ -31,7 +31,7 @@ block content
```javascript
// getting-started.js
- var mongoose = require('mongoose');
+ const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/test', {useNewUrlParser: true});
```
@@ -40,7 +40,7 @@ block content
error occurs:
```javascript
- var db = mongoose.connection;
+ const db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', function() {
// we're connected!
@@ -54,7 +54,7 @@ block content
Let's get a reference to it and define our kittens.
```javascript
- var kittySchema = new mongoose.Schema({
+ const kittySchema = new mongoose.Schema({
name: String
});
```
@@ -62,7 +62,7 @@ block content
So far so good. We've got a schema with one property, `name`, which will be a `String`. The next step is compiling our schema into a [Model](/docs/models.html).
```javascript
- var Kitten = mongoose.model('Kitten', kittySchema);
+ const Kitten = mongoose.model('Kitten', kittySchema);
```
A model is a class with which we construct documents.
@@ -70,7 +70,7 @@ block content
Let's create a kitten document representing the little guy we just met on the sidewalk outside:
```javascript
- var silence = new Kitten({ name: 'Silence' });
+ const silence = new Kitten({ name: 'Silence' });
console.log(silence.name); // 'Silence'
```
@@ -80,20 +80,20 @@ block content
```javascript
// NOTE: methods must be added to the schema before compiling it with mongoose.model()
kittySchema.methods.speak = function () {
- var greeting = this.name
+ const greeting = this.name
? "Meow name is " + this.name
: "I don't have a name";
console.log(greeting);
}
- var Kitten = mongoose.model('Kitten', kittySchema);
+ const Kitten = mongoose.model('Kitten', kittySchema);
```
Functions added to the `methods` property of a schema get compiled into
the `Model` prototype and exposed on each document instance:
```javascript
- var fluffy = new Kitten({ name: 'fluffy' });
+ const fluffy = new Kitten({ name: 'fluffy' });
fluffy.speak(); // "Meow name is fluffy"
```
diff --git a/docs/jest.pug b/docs/jest.pug
index 2cb80547c36..16c5682b3c3 100644
--- a/docs/jest.pug
+++ b/docs/jest.pug
@@ -23,10 +23,11 @@ block content
Jest is a client-side JavaScript testing library developed by Facebook.
- It was one of the libraries affected by [Facebook's licensing scandal in 2017](https://www.theregister.co.uk/2017/09/22/facebook_will_free_react_other_code_from_unloved_license/).
Because Jest is designed primarily for testing React applications, using
it to test Node.js server-side applications comes with a lot of caveats.
- We strongly recommend using [Mocha](http://npmjs.com/package/mocha) instead.
+ We strongly advise against using Jest for testing any Node.js apps unless
+ you are an expert developer with an intimate knowledge of Jest.
+
If you choose to delve into dangerous waters and test Mongoose apps with
Jest, here's what you need to know:
@@ -87,6 +88,14 @@ block content
sinon.stub(time, 'setTimeout');
```
+ ## `globalSetup` and `globalTeardown`
+
+ Do **not** use `globalSetup` to call `mongoose.connect()` or
+ `mongoose.createConnection()`. Jest runs `globalSetup` in
+ a [separate environment](https://github.com/facebook/jest/issues/7184),
+ so you cannot use any connections you create in `globalSetup`
+ in your tests.
+
## Further Reading
Want to learn more about testing Mongoose apps? The
diff --git a/docs/models.pug b/docs/models.pug
index f3cd5e8540f..b47da9b52b5 100644
--- a/docs/models.pug
+++ b/docs/models.pug
@@ -52,7 +52,7 @@ block content
```
:markdown
The first argument is the _singular_ name of the collection your model is
- for. ** Mongoose automatically looks for the plural, lowercased version of your model name. **
+ for. **Mongoose automatically looks for the plural, lowercased version of your model name.**
Thus, for the example above, the model Tank is for the **tanks** collection
in the database.
diff --git a/docs/schematypes.pug b/docs/schematypes.pug
index 0e345936718..fc40f23da01 100644
--- a/docs/schematypes.pug
+++ b/docs/schematypes.pug
@@ -81,6 +81,7 @@ block content
- [Array](#arrays)
- [Decimal128](./api.html#mongoose_Mongoose-Decimal128)
- [Map](#maps)
+ - [Schema](#schemas)
Example
@@ -662,6 +663,28 @@ block content
schema.path('arr.0.url').get(v => `${root}${v}`);
```
+
+
+ To declare a path as another [schema](./guide.html#definition),
+ set `type` to the sub-schema's instance.
+
+ To set a default value based on the sub-schema's shape, simply set a default value,
+ and the value will be cast based on the sub-schema's definition before being set
+ during document creation.
+
+ ```javascript
+ const subSchema = new mongoose.Schema({
+ // some schema definition here
+ });
+
+ const schema = new mongoose.Schema({
+ data: {
+ type: subSchema
+ default: {}
+ }
+ });
+ ```
+
Mongoose can also be extended with [custom SchemaTypes](customschematypes.html). Search the
diff --git a/docs/subdocs.pug b/docs/subdocs.pug
index 78588429573..50b4c31b051 100644
--- a/docs/subdocs.pug
+++ b/docs/subdocs.pug
@@ -44,6 +44,7 @@ block content
:markdown
- What is a Subdocument?
+ - Subdocuments versus Nested Paths
- Finding a Subdocument
- Adding Subdocs to Arrays
- Removing Subdocs
@@ -110,8 +111,8 @@ block content
});
var parentSchema = new mongoose.Schema({
- child: childSchema,
- });
+ child: childSchema
+ });
parentSchema.pre('validate', function(next) {
console.log('1');
@@ -124,6 +125,59 @@ block content
});
```
+ ### Subdocuments versus Nested Paths
+
+ In Mongoose, nested paths are subtly different from subdocuments.
+ For example, below are two schemas: one with `child` as a subdocument,
+ and one with `child` as a nested path.
+
+ ```javascript
+ // Subdocument
+ const subdocumentSchema = new mongoose.Schema({
+ child: new mongoose.Schema({ name: String, age: Number })
+ });
+ const Subdoc = mongoose.model('Subdoc', subdocumentSchema);
+
+ // Nested path
+ const nestedSchema = new mongoose.Schema({
+ child: { name: String, age: Number }
+ });
+ const Nested = mongoose.model('Nested', nestedSchema);
+ ```
+
+ These two schemas look similar, and the documents in MongoDB will
+ have the same structure with both schemas. But there are a few
+ Mongoose-specific differences:
+
+ First, instances of `Nested` never have `child === undefined`.
+ You can always set subproperties of `child`, even if you don't set
+ the `child` property. But instances of `Subdoc` can have `child === undefined`.
+
+ ```javascript
+ const doc1 = new Subdoc({});
+ doc1.child === undefined; // true
+ doc1.child.name = 'test'; // Throws TypeError: cannot read property...
+
+ const doc2 = new Nested({});
+ doc2.child === undefined; // false
+ console.log(doc2.child); // Prints 'MongooseDocument { undefined }'
+ doc2.child.name = 'test'; // Works
+ ```
+
+ Secondly, in Mongoose 5, [`Document#set()`](/docs/api/document.html#document_Document-set)
+ merges when you call it on a nested path, but overwrites when you call
+ it on a subdocument.
+
+ ```javascript
+ const doc1 = new Subdoc({ child: { name: 'Luke', age: 19 } });
+ doc1.set({ child: { age: 21 } });
+ doc1.child; // { age: 21 }
+
+ const doc2 = new Nested({ child: { name: 'Luke', age: 19 } });
+ doc2.set({ child: { age: 21 } });
+ doc2.child; // { name: Luke, age: 21 }
+ ```
+
h3#finding-a-subdocument Finding a Subdocument
:markdown
Each subdocument has an `_id` by default. Mongoose document arrays have a
@@ -222,8 +276,9 @@ block content
h4#altsyntaxarrays Alternate declaration syntax for arrays
:markdown
- If you create a schema with an array of objects, mongoose will automatically
+ If you create a schema with an array of objects, Mongoose will automatically
convert the object to a schema for you:
+
```javascript
var parentSchema = new Schema({
children: [{ name: 'string' }]
@@ -234,25 +289,58 @@ block content
});
```
- h4#altsyntaxsingle Alternate declaration syntax for single subdocuments
+ h4#altsyntaxsingle Alternate declaration syntax for single nested subdocuments
:markdown
- Similarly, single subdocuments also have a shorthand whereby you can omit
- wrapping the schema with an instance of Schema. However, for historical
- reasons, this alternate declaration must be enabled via an option (either
- on the parent schema instantiation or on the mongoose instance).
+ Unlike document arrays, Mongoose 5 does not convert an objects in schemas
+ into nested schemas. In the below example, `nested` is a _nested path_
+ rather than a subdocument.
+
```javascript
- var parentSchema = new Schema({
- child: { type: { name: 'string' } }
- }, { typePojoToMixed: false });
- // Equivalent
- var parentSchema = new Schema({
- child: new Schema({ name: 'string' })
+ const schema = new Schema({
+ nested: {
+ prop: String
+ }
});
- // Not equivalent! Careful - a Mixed path is created instead!
- var parentSchema = new Schema({
- child: { type: { name: 'string' } }
+ ```
+
+ This leads to some surprising behavior when you attempt to define a
+ nested path with validators or getters/setters.
+
+ ```javascript
+ const schema = new Schema({
+ nested: {
+ // Do not do this! This makes `nested` a mixed path in Mongoose 5
+ type: { prop: String },
+ required: true
+ }
+ });
+
+ const schema = new Schema({
+ nested: {
+ // This works correctly
+ type: new Schema({ prop: String }),
+ required: true
+ }
});
```
+
+ Surprisingly, declaring `nested` with an object `type` makes `nested`
+ into a path of type [Mixed](/docs/schematypes.html#mixed). To instead
+ make Mongoose automatically convert `type: { prop: String }` into
+ `type: new Schema({ prop: String })`, set the `typePojoToMixed` option
+ to `false`.
+
+ ```javascript
+ const schema = new Schema({
+ nested: {
+ // Because of `typePojoToMixed`, Mongoose knows to
+ // wrap `{ prop: String }` in a `new Schema()`.
+ type: { prop: String },
+ required: true
+ }
+ }, { typePojoToMixed: false });
+ ```
+
h3#next Next Up
:markdown
Now that we've covered Subdocuments, let's take a look at
diff --git a/index.pug b/index.pug
index 6c9b57e908c..240258cb827 100644
--- a/index.pug
+++ b/index.pug
@@ -235,18 +235,12 @@ html(lang='en')
-
-
-
-
-
-
@@ -325,9 +319,6 @@ html(lang='en')
-
-
-
@@ -370,6 +361,9 @@ html(lang='en')
+
+
+
diff --git a/lib/aggregate.js b/lib/aggregate.js
index cc31f09b98d..d275c2ed4da 100644
--- a/lib/aggregate.js
+++ b/lib/aggregate.js
@@ -909,6 +909,32 @@ Aggregate.prototype.facet = function(options) {
return this.append({ $facet: options });
};
+/**
+ * Helper for [Atlas Text Search](https://docs.atlas.mongodb.com/reference/atlas-search/tutorial/)'s
+ * `$search` stage.
+ *
+ * ####Example:
+ *
+ * Model.aggregate().
+ * search({
+ * text: {
+ * query: 'baseball',
+ * path: 'plot'
+ * }
+ * });
+ *
+ * // Output: [{ plot: '...', title: '...' }]
+ *
+ * @param {Object} $search options
+ * @return {Aggregate} this
+ * @see $search https://docs.atlas.mongodb.com/reference/atlas-search/tutorial/
+ * @api public
+ */
+
+Aggregate.prototype.search = function(options) {
+ return this.append({ $search: options });
+};
+
/**
* Returns the current pipeline
*
diff --git a/lib/connection.js b/lib/connection.js
index c64e192c1f9..e5b3187154b 100644
--- a/lib/connection.js
+++ b/lib/connection.js
@@ -22,6 +22,8 @@ const utils = require('./utils');
const parseConnectionString = require('mongodb/lib/core').parseConnectionString;
+const sessionNewDocuments = require('./helpers/symbols').sessionNewDocuments;
+
let id = 0;
/*!
@@ -417,6 +419,57 @@ Connection.prototype.startSession = _wrapConnHelper(function startSession(option
cb(null, session);
});
+/**
+ * _Requires MongoDB >= 3.6.0._ Executes the wrapped async function
+ * in a transaction. Mongoose will commit the transaction if the
+ * async function executes successfully and attempt to retry if
+ * there was a retriable error.
+ *
+ * Calls the MongoDB driver's [`session.withTransaction()`](http://mongodb.github.io/node-mongodb-native/3.5/api/ClientSession.html#withTransaction),
+ * but also handles resetting Mongoose document state as shown below.
+ *
+ * ####Example:
+ *
+ * const doc = new Person({ name: 'Will Riker' });
+ * await db.transaction(async function setRank(session) {
+ * doc.rank = 'Captain';
+ * await doc.save({ session });
+ * doc.isNew; // false
+ *
+ * // Throw an error to abort the transaction
+ * throw new Error('Oops!');
+ * }).catch(() => {});
+ *
+ * // true, `transaction()` reset the document's state because the
+ * // transaction was aborted.
+ * doc.isNew;
+ *
+ * @method transaction
+ * @param {Function} fn Function to execute in a transaction
+ * @return {Promise} promise that resolves to the returned value of `fn`
+ * @api public
+ */
+
+Connection.prototype.transaction = function transaction(fn) {
+ return this.startSession().then(session => {
+ session[sessionNewDocuments] = [];
+ return session.withTransaction(() => fn(session)).
+ then(res => {
+ delete session[sessionNewDocuments];
+ return res;
+ }).
+ catch(err => {
+ // If transaction was aborted, we need to reset newly
+ // inserted documents' `isNew`.
+ for (const doc of session[sessionNewDocuments]) {
+ doc.isNew = true;
+ }
+ delete session[sessionNewDocuments];
+ throw err;
+ });
+ });
+};
+
/**
* Helper for `dropCollection()`. Will delete the given collection, including
* all documents and indexes.
diff --git a/lib/document.js b/lib/document.js
index c20bdf95a2f..7ce49f51ff5 100644
--- a/lib/document.js
+++ b/lib/document.js
@@ -55,7 +55,8 @@ const specialProperties = utils.specialProperties;
*
* @param {Object} obj the values to set
* @param {Object} [fields] optional object containing the fields which were selected in the query returning this document and any populated paths data
- * @param {Boolean} [skipId] bool, should we auto create an ObjectId _id
+ * @param {Object} [options] various configuration options for the document
+ * @param {Boolean} [options.defaults=true] if `false`, skip applying default values to this document.
* @inherits NodeJS EventEmitter http://nodejs.org/api/events.html#events_class_events_eventemitter
* @event `init`: Emitted on a document after it has been retrieved from the db and fully hydrated by Mongoose.
* @event `save`: Emitted when the document is successfully saved
@@ -67,7 +68,9 @@ function Document(obj, fields, skipId, options) {
options = skipId;
skipId = options.skipId;
}
- options = options || {};
+ options = Object.assign({}, options);
+ const defaults = get(options, 'defaults', true);
+ options.defaults = defaults;
// Support `browserDocument.js` syntax
if (this.schema == null) {
@@ -126,9 +129,11 @@ function Document(obj, fields, skipId, options) {
// By default, defaults get applied **before** setting initial values
// Re: gh-6155
- $__applyDefaults(this, fields, skipId, exclude, hasIncludedChildren, true, {
- isNew: this.isNew
- });
+ if (defaults) {
+ $__applyDefaults(this, fields, skipId, exclude, hasIncludedChildren, true, {
+ isNew: this.isNew
+ });
+ }
}
if (obj) {
@@ -147,13 +152,13 @@ function Document(obj, fields, skipId, options) {
// Function defaults get applied **after** setting initial values so they
// see the full doc rather than an empty one, unless they opt out.
// Re: gh-3781, gh-6155
- if (options.willInit) {
+ if (options.willInit && defaults) {
EventEmitter.prototype.once.call(this, 'init', () => {
$__applyDefaults(this, fields, skipId, exclude, hasIncludedChildren, false, options.skipDefaults, {
isNew: this.isNew
});
});
- } else {
+ } else if (defaults) {
$__applyDefaults(this, fields, skipId, exclude, hasIncludedChildren, false, options.skipDefaults, {
isNew: this.isNew
});
diff --git a/lib/helpers/query/getEmbeddedDiscriminatorPath.js b/lib/helpers/query/getEmbeddedDiscriminatorPath.js
index ff297ace6ff..395ea75c4bf 100644
--- a/lib/helpers/query/getEmbeddedDiscriminatorPath.js
+++ b/lib/helpers/query/getEmbeddedDiscriminatorPath.js
@@ -2,6 +2,7 @@
const cleanPositionalOperators = require('../schema/cleanPositionalOperators');
const get = require('../get');
+const getDiscriminatorByValue = require('../discriminator/getDiscriminatorByValue');
/*!
* Like `schema.path()`, except with a document, because impossible to
@@ -26,7 +27,6 @@ module.exports = function getEmbeddedDiscriminatorPath(schema, update, filter, p
type = schema.pathType(subpath);
if ((schematype.$isSingleNested || schematype.$isMongooseDocumentArrayElement) &&
schematype.schema.discriminators != null) {
- const discriminators = schematype.schema.discriminators;
const key = get(schematype, 'schema.options.discriminatorKey');
const discriminatorValuePath = subpath + '.' + key;
const discriminatorFilterPath =
@@ -49,13 +49,16 @@ module.exports = function getEmbeddedDiscriminatorPath(schema, update, filter, p
discriminatorKey = update[discriminatorValuePath];
}
- if (discriminatorKey == null || discriminators[discriminatorKey] == null) {
+ if (discriminatorKey == null) {
continue;
}
+
+ const discriminatorSchema = getDiscriminatorByValue(schematype.caster, discriminatorKey).schema;
+
const rest = parts.slice(i + 1).join('.');
- schematype = discriminators[discriminatorKey].path(rest);
+ schematype = discriminatorSchema.path(rest);
if (schematype != null) {
- type = discriminators[discriminatorKey]._getPathType(rest);
+ type = discriminatorSchema._getPathType(rest);
break;
}
}
diff --git a/lib/helpers/schema/getIndexes.js b/lib/helpers/schema/getIndexes.js
index ce4286be45a..52a275ee607 100644
--- a/lib/helpers/schema/getIndexes.js
+++ b/lib/helpers/schema/getIndexes.js
@@ -125,6 +125,7 @@ module.exports = function getIndexes(schema) {
const len = subindexes.length;
for (let i = 0; i < len; ++i) {
const indexObj = subindexes[i][0];
+ const indexOptions = subindexes[i][1];
const keys = Object.keys(indexObj);
const klen = keys.length;
const newindex = {};
@@ -135,7 +136,17 @@ module.exports = function getIndexes(schema) {
newindex[prefix + key] = indexObj[key];
}
- indexes.push([newindex, subindexes[i][1]]);
+ const newIndexOptions = Object.assign({}, indexOptions);
+ if (indexOptions != null && indexOptions.partialFilterExpression != null) {
+ newIndexOptions.partialFilterExpression = {};
+ const partialFilterExpression = indexOptions.partialFilterExpression;
+ for (const key of Object.keys(partialFilterExpression)) {
+ newIndexOptions.partialFilterExpression[prefix + key] =
+ partialFilterExpression[key];
+ }
+ }
+
+ indexes.push([newindex, newIndexOptions]);
}
}
};
diff --git a/lib/helpers/setDefaultsOnInsert.js b/lib/helpers/setDefaultsOnInsert.js
index 0545e80d3d4..747cb61ab8d 100644
--- a/lib/helpers/setDefaultsOnInsert.js
+++ b/lib/helpers/setDefaultsOnInsert.js
@@ -14,6 +14,17 @@ const modifiedPaths = require('./common').modifiedPaths;
*/
module.exports = function(filter, schema, castedDoc, options) {
+ options = options || {};
+
+ const shouldSetDefaultsOnInsert =
+ options.hasOwnProperty('setDefaultsOnInsert') ?
+ options.setDefaultsOnInsert :
+ schema.base.options.setDefaultsOnInsert;
+
+ if (!options.upsert || !shouldSetDefaultsOnInsert) {
+ return castedDoc;
+ }
+
const keys = Object.keys(castedDoc || {});
const updatedKeys = {};
const updatedValues = {};
@@ -22,12 +33,6 @@ module.exports = function(filter, schema, castedDoc, options) {
let hasDollarUpdate = false;
- options = options || {};
-
- if (!options.upsert || !options.setDefaultsOnInsert) {
- return castedDoc;
- }
-
for (let i = 0; i < numKeys; ++i) {
if (keys[i].startsWith('$')) {
modifiedPaths(castedDoc[keys[i]], '', modified);
diff --git a/lib/helpers/symbols.js b/lib/helpers/symbols.js
index 746f8935557..ef0ddd573f2 100644
--- a/lib/helpers/symbols.js
+++ b/lib/helpers/symbols.js
@@ -11,5 +11,6 @@ exports.modelSymbol = Symbol('mongoose#Model');
exports.objectIdSymbol = Symbol('mongoose#ObjectId');
exports.populateModelSymbol = Symbol('mongoose.PopulateOptions#Model');
exports.schemaTypeSymbol = Symbol('mongoose#schemaType');
+exports.sessionNewDocuments = Symbol('mongoose:ClientSession#newDocuments');
exports.scopeSymbol = Symbol('mongoose#Document#scope');
exports.validatorErrorSymbol = Symbol('mongoose:validatorError');
\ No newline at end of file
diff --git a/lib/helpers/update/applyTimestampsToUpdate.js b/lib/helpers/update/applyTimestampsToUpdate.js
index 7318232b38b..27d6e32ca3b 100644
--- a/lib/helpers/update/applyTimestampsToUpdate.js
+++ b/lib/helpers/update/applyTimestampsToUpdate.js
@@ -52,7 +52,28 @@ function applyTimestampsToUpdate(now, createdAt, updatedAt, currentUpdate, optio
updates.$set = updates.$set || {};
if (!skipUpdatedAt && updatedAt &&
(!currentUpdate.$currentDate || !currentUpdate.$currentDate[updatedAt])) {
- updates.$set[updatedAt] = now;
+ let timestampSet = false;
+ if (updatedAt.indexOf('.') !== -1) {
+ const pieces = updatedAt.split('.');
+ for (let i = 1; i < pieces.length; ++i) {
+ const remnant = pieces.slice(-i).join('.');
+ const start = pieces.slice(0, -i).join('.');
+ if (currentUpdate[start] != null) {
+ currentUpdate[start][remnant] = now;
+ timestampSet = true;
+ break;
+ } else if (currentUpdate.$set && currentUpdate.$set[start]) {
+ currentUpdate.$set[start][remnant] = now;
+ timestampSet = true;
+ break;
+ }
+ }
+ }
+
+ if (!timestampSet) {
+ updates.$set[updatedAt] = now;
+ }
+
if (updates.hasOwnProperty(updatedAt)) {
delete updates[updatedAt];
}
@@ -66,8 +87,28 @@ function applyTimestampsToUpdate(now, createdAt, updatedAt, currentUpdate, optio
delete currentUpdate.$set[createdAt];
}
- updates.$setOnInsert = updates.$setOnInsert || {};
- updates.$setOnInsert[createdAt] = now;
+ let timestampSet = false;
+ if (createdAt.indexOf('.') !== -1) {
+ const pieces = createdAt.split('.');
+ for (let i = 1; i < pieces.length; ++i) {
+ const remnant = pieces.slice(-i).join('.');
+ const start = pieces.slice(0, -i).join('.');
+ if (currentUpdate[start] != null) {
+ currentUpdate[start][remnant] = now;
+ timestampSet = true;
+ break;
+ } else if (currentUpdate.$set && currentUpdate.$set[start]) {
+ currentUpdate.$set[start][remnant] = now;
+ timestampSet = true;
+ break;
+ }
+ }
+ }
+
+ if (!timestampSet) {
+ updates.$setOnInsert = updates.$setOnInsert || {};
+ updates.$setOnInsert[createdAt] = now;
+ }
}
if (Object.keys(updates.$set).length === 0) {
diff --git a/lib/helpers/update/castArrayFilters.js b/lib/helpers/update/castArrayFilters.js
index 471fda35a38..57018d9fb10 100644
--- a/lib/helpers/update/castArrayFilters.js
+++ b/lib/helpers/update/castArrayFilters.js
@@ -38,40 +38,41 @@ module.exports = function castArrayFilters(query) {
if (filter == null) {
throw new Error(`Got null array filter in ${arrayFilters}`);
}
- const firstKey = Object.keys(filter)[0];
+ for (const key in filter) {
- if (filter[firstKey] == null) {
- continue;
- }
+ if (filter[key] == null) {
+ continue;
+ }
- const dot = firstKey.indexOf('.');
- let filterPath = dot === -1 ?
- updatedPathsByFilter[firstKey] + '.0' :
- updatedPathsByFilter[firstKey.substr(0, dot)] + '.0' + firstKey.substr(dot);
+ const dot = key.indexOf('.');
+ let filterPath = dot === -1 ?
+ updatedPathsByFilter[key] + '.0' :
+ updatedPathsByFilter[key.substr(0, dot)] + '.0' + key.substr(dot);
- if (filterPath == null) {
- throw new Error(`Filter path not found for ${firstKey}`);
- }
+ if (filterPath == null) {
+ throw new Error(`Filter path not found for ${key}`);
+ }
- // If there are multiple array filters in the path being updated, make sure
- // to replace them so we can get the schema path.
- filterPath = cleanPositionalOperators(filterPath);
+ // If there are multiple array filters in the path being updated, make sure
+ // to replace them so we can get the schema path.
+ filterPath = cleanPositionalOperators(filterPath);
- const schematype = getPath(schema, filterPath);
- if (schematype == null) {
- if (!strictQuery) {
- return;
+ const schematype = getPath(schema, filterPath);
+ if (schematype == null) {
+ if (!strictQuery) {
+ return;
+ }
+ // For now, treat `strictQuery = true` and `strictQuery = 'throw'` as
+ // equivalent for casting array filters. `strictQuery = true` doesn't
+ // quite work in this context because we never want to silently strip out
+ // array filters, even if the path isn't in the schema.
+ throw new Error(`Could not find path "${filterPath}" in schema`);
+ }
+ if (typeof filter[key] === 'object') {
+ filter[key] = castFilterPath(query, schematype, filter[key]);
+ } else {
+ filter[key] = schematype.castForQuery(filter[key]);
}
- // For now, treat `strictQuery = true` and `strictQuery = 'throw'` as
- // equivalent for casting array filters. `strictQuery = true` doesn't
- // quite work in this context because we never want to silently strip out
- // array filters, even if the path isn't in the schema.
- throw new Error(`Could not find path "${filterPath}" in schema`);
- }
- if (typeof filter[firstKey] === 'object') {
- filter[firstKey] = castFilterPath(query, schematype, filter[firstKey]);
- } else {
- filter[firstKey] = schematype.castForQuery(filter[firstKey]);
}
}
};
\ No newline at end of file
diff --git a/lib/index.js b/lib/index.js
index c1c8b996407..a72fdddffc6 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -34,6 +34,7 @@ const pkg = require('../package.json');
const cast = require('./cast');
const removeSubdocs = require('./plugins/removeSubdocs');
const saveSubdocs = require('./plugins/saveSubdocs');
+const trackTransaction = require('./plugins/trackTransaction');
const validateBeforeSave = require('./plugins/validateBeforeSave');
const Aggregate = require('./aggregate');
@@ -106,7 +107,8 @@ function Mongoose(options) {
[saveSubdocs, { deduplicate: true }],
[validateBeforeSave, { deduplicate: true }],
[shardingPlugin, { deduplicate: true }],
- [removeSubdocs, { deduplicate: true }]
+ [removeSubdocs, { deduplicate: true }],
+ [trackTransaction, { deduplicate: true }]
]
});
}
@@ -158,6 +160,7 @@ Mongoose.prototype.driver = require('./driver');
* - 'toObject': `{ transform: true, flattenDecimals: true }` by default. Overwrites default objects to [`toObject()`](/docs/api.html#document_Document-toObject)
* - 'toJSON': `{ transform: true, flattenDecimals: true }` by default. Overwrites default objects to [`toJSON()`](/docs/api.html#document_Document-toJSON), for determining how Mongoose documents get serialized by `JSON.stringify()`
* - 'strict': true by default, may be `false`, `true`, or `'throw'`. Sets the default strict mode for schemas.
+ * - 'strictQuery': false by default, may be `false`, `true`, or `'throw'`. Sets the default [strictQuery](/docs/guide.html#strictQuery) mode for schemas.
* - 'selectPopulatedPaths': true by default. Set to false to opt out of Mongoose adding all fields that you `populate()` to your `select()`. The schema-level option `selectPopulatedPaths` overwrites this one.
* - 'typePojoToMixed': true by default, may be `false` or `true`. Sets the default typePojoToMixed for schemas.
* - 'maxTimeMS': If set, attaches [maxTimeMS](https://docs.mongodb.com/manual/reference/operator/meta/maxTimeMS/) to every query
diff --git a/lib/plugins/trackTransaction.js b/lib/plugins/trackTransaction.js
new file mode 100644
index 00000000000..c307f9b759a
--- /dev/null
+++ b/lib/plugins/trackTransaction.js
@@ -0,0 +1,20 @@
+'use strict';
+
+const sessionNewDocuments = require('../helpers/symbols').sessionNewDocuments;
+
+module.exports = function trackTransaction(schema) {
+ schema.pre('save', function() {
+ if (!this.isNew) {
+ return;
+ }
+
+ const session = this.$session();
+ if (session == null) {
+ return;
+ }
+ if (session.transaction == null || session[sessionNewDocuments] == null) {
+ return;
+ }
+ session[sessionNewDocuments].push(this);
+ });
+};
\ No newline at end of file
diff --git a/lib/schema.js b/lib/schema.js
index 062b5be8321..1461a95ac3a 100644
--- a/lib/schema.js
+++ b/lib/schema.js
@@ -400,6 +400,7 @@ Schema.prototype.defaultOptions = function(options) {
const baseOptions = get(this, 'base.options', {});
options = utils.options({
strict: 'strict' in baseOptions ? baseOptions.strict : true,
+ strictQuery: 'strictQuery' in baseOptions ? baseOptions.strictQuery : false,
bufferCommands: true,
capped: false, // { size, max, autoIndexId }
versionKey: '__v',
@@ -935,6 +936,14 @@ Schema.prototype.interpretAsType = function(path, obj, options) {
if (options.hasOwnProperty('typePojoToMixed')) {
childSchemaOptions.typePojoToMixed = options.typePojoToMixed;
}
+
+ if (this._userProvidedOptions.hasOwnProperty('_id')) {
+ childSchemaOptions._id = this._userProvidedOptions._id;
+ } else if (Schema.Types.DocumentArray.defaultOptions &&
+ Schema.Types.DocumentArray.defaultOptions._id != null) {
+ childSchemaOptions._id = Schema.Types.DocumentArray.defaultOptions._id;
+ }
+
const childSchema = new Schema(cast, childSchemaOptions);
childSchema.$implicitlyCreated = true;
return new MongooseTypes.DocumentArray(path, childSchema, obj);
diff --git a/lib/schema/SingleNestedPath.js b/lib/schema/SingleNestedPath.js
index d7abd6cea77..c23aa477da6 100644
--- a/lib/schema/SingleNestedPath.js
+++ b/lib/schema/SingleNestedPath.js
@@ -300,6 +300,26 @@ SingleNestedPath.prototype.discriminator = function(name, schema, value) {
return this.caster.discriminators[name];
};
+/**
+ * Sets a default option for all SingleNestedPath instances.
+ *
+ * ####Example:
+ *
+ * // Make all numbers have option `min` equal to 0.
+ * mongoose.Schema.Embedded.set('required', true);
+ *
+ * @param {String} option - The option you'd like to set the value for
+ * @param {*} value - value for option
+ * @return {undefined}
+ * @function set
+ * @static
+ * @api public
+ */
+
+SingleNestedPath.defaultOptions = {};
+
+SingleNestedPath.set = SchemaType.set;
+
/*!
* ignore
*/
@@ -308,6 +328,9 @@ SingleNestedPath.prototype.clone = function() {
const options = Object.assign({}, this.options);
const schematype = new this.constructor(this.schema, this.path, options);
schematype.validators = this.validators.slice();
+ if (this.requiredValidator !== undefined) {
+ schematype.requiredValidator = this.requiredValidator;
+ }
schematype.caster.discriminators = Object.assign({}, this.caster.discriminators);
return schematype;
};
diff --git a/lib/schema/array.js b/lib/schema/array.js
index bc828a178ab..8e7e8cb989d 100644
--- a/lib/schema/array.js
+++ b/lib/schema/array.js
@@ -408,6 +408,9 @@ SchemaArray.prototype.clone = function() {
const options = Object.assign({}, this.options);
const schematype = new this.constructor(this.path, this.caster, options, this.schemaOptions);
schematype.validators = this.validators.slice();
+ if (this.requiredValidator !== undefined) {
+ schematype.requiredValidator = this.requiredValidator;
+ }
return schematype;
};
diff --git a/lib/schema/documentarray.js b/lib/schema/documentarray.js
index 57e2fa441b0..dfe25f45e38 100644
--- a/lib/schema/documentarray.js
+++ b/lib/schema/documentarray.js
@@ -465,6 +465,9 @@ DocumentArrayPath.prototype.clone = function() {
const options = Object.assign({}, this.options);
const schematype = new this.constructor(this.path, this.schema, options, this.schemaOptions);
schematype.validators = this.validators.slice();
+ if (this.requiredValidator !== undefined) {
+ schematype.requiredValidator = this.requiredValidator;
+ }
schematype.Constructor.discriminators = Object.assign({},
this.Constructor.discriminators);
return schematype;
@@ -510,6 +513,26 @@ function scopePaths(array, fields, init) {
return hasKeys && selected || undefined;
}
+/**
+ * Sets a default option for all DocumentArray instances.
+ *
+ * ####Example:
+ *
+ * // Make all numbers have option `min` equal to 0.
+ * mongoose.Schema.DocumentArray.set('_id', false);
+ *
+ * @param {String} option - The option you'd like to set the value for
+ * @param {*} value - value for option
+ * @return {undefined}
+ * @function set
+ * @static
+ * @api public
+ */
+
+DocumentArrayPath.defaultOptions = {};
+
+DocumentArrayPath.set = SchemaType.set;
+
/*!
* Module exports.
*/
diff --git a/lib/types/subdocument.js b/lib/types/subdocument.js
index 521237aee72..63cd42c0c11 100644
--- a/lib/types/subdocument.js
+++ b/lib/types/subdocument.js
@@ -29,7 +29,10 @@ function Subdocument(value, fields, parent, skipId, options) {
}
if (parent != null) {
// If setting a nested path, should copy isNew from parent re: gh-7048
- options = Object.assign({}, options, { isNew: parent.isNew });
+ options = Object.assign({}, options, {
+ isNew: parent.isNew,
+ defaults: parent.$__.$options.defaults
+ });
}
Document.call(this, value, fields, skipId, options);
diff --git a/lib/validoptions.js b/lib/validoptions.js
index 9464e4d8611..a9c8a352d23 100644
--- a/lib/validoptions.js
+++ b/lib/validoptions.js
@@ -16,7 +16,9 @@ const VALID_OPTIONS = Object.freeze([
'objectIdGetter',
'runValidators',
'selectPopulatedPaths',
+ 'setDefaultsOnInsert',
'strict',
+ 'strictQuery',
'toJSON',
'toObject',
'useCreateIndex',
diff --git a/package.json b/package.json
index 4d52679f515..a719c94ab89 100644
--- a/package.json
+++ b/package.json
@@ -21,7 +21,7 @@
"dependencies": {
"bson": "^1.1.4",
"kareem": "2.3.1",
- "mongodb": "3.5.8",
+ "mongodb": "3.5.9",
"mongoose-legacy-pluralize": "1.0.2",
"mpath": "0.7.0",
"mquery": "3.2.2",
diff --git a/test/aggregate.test.js b/test/aggregate.test.js
index 8fc56d44b97..3c0239b26ca 100644
--- a/test/aggregate.test.js
+++ b/test/aggregate.test.js
@@ -430,15 +430,12 @@ describe('aggregate: ', function() {
});
});
- it('Throws if no options are passed to graphLookup', function(done) {
+ it('Throws if no options are passed to graphLookup', function() {
const aggregate = new Aggregate();
- try {
+ assert.throws(function() {
aggregate.graphLookup('invalid options');
- done(new Error('Should have errored'));
- } catch (error) {
- assert.ok(error instanceof TypeError);
- done();
- }
+ },
+ TypeError);
});
});
@@ -547,15 +544,11 @@ describe('aggregate: ', function() {
[{ $sortByCount: { lname: '$employee.last' } }]);
});
- it('throws if the argument is neither a string or object', function(done) {
+ it('throws if the argument is neither a string or object', function() {
const aggregate = new Aggregate();
- try {
+ assert.throws(function() {
aggregate.sortByCount(1);
- done(new Error('Should have errored'));
- } catch (error) {
- assert.ok(error instanceof TypeError);
- done();
- }
+ }, TypeError);
});
});
});
@@ -642,7 +635,7 @@ describe('aggregate: ', function() {
});
});
- it('unwind with obj', function(done) {
+ it('unwind with obj', function() {
const aggregate = new Aggregate();
const agg = aggregate.
@@ -652,10 +645,9 @@ describe('aggregate: ', function() {
assert.equal(agg._pipeline.length, 1);
assert.strictEqual(agg._pipeline[0].$unwind.preserveNullAndEmptyArrays,
true);
- done();
});
- it('unwind throws with bad arg', function(done) {
+ it('unwind throws with bad arg', function() {
const aggregate = new Aggregate();
let threw = false;
@@ -668,7 +660,6 @@ describe('aggregate: ', function() {
threw = true;
}
assert.ok(threw);
- done();
});
it('match', function(done) {
@@ -815,7 +806,7 @@ describe('aggregate: ', function() {
});
});
- it('pipeline() (gh-5825)', function(done) {
+ it('pipeline() (gh-5825)', function() {
const aggregate = new Aggregate();
const pipeline = aggregate.
@@ -824,7 +815,6 @@ describe('aggregate: ', function() {
pipeline();
assert.deepEqual(pipeline, [{ $match: { sal: { $lt: 16000 } } }]);
- done();
});
it('explain()', function(done) {
@@ -883,7 +873,7 @@ describe('aggregate: ', function() {
});
describe('error when not bound to a model', function() {
- it('with callback', function(done) {
+ it('with callback', function() {
const aggregate = new Aggregate();
aggregate.skip(0);
@@ -895,8 +885,6 @@ describe('aggregate: ', function() {
assert.equal(error.message, 'Aggregate not bound to any Model');
}
assert.ok(threw);
-
- done();
});
});
@@ -1119,7 +1107,7 @@ describe('aggregate: ', function() {
});
});
- it('readPref from schema (gh-5522)', function(done) {
+ it('readPref from schema (gh-5522)', function() {
const schema = new Schema({ name: String }, { read: 'secondary' });
const M = db.model('Test', schema);
const a = M.aggregate();
@@ -1128,8 +1116,6 @@ describe('aggregate: ', function() {
a.read('secondaryPreferred');
assert.equal(a.options.readPreference.mode, 'secondaryPreferred');
-
- done();
});
});
@@ -1175,7 +1161,7 @@ describe('aggregate: ', function() {
});
});
- it('cursor() with useMongooseAggCursor (gh-5145)', function(done) {
+ it('cursor() with useMongooseAggCursor (gh-5145)', function() {
const MyModel = db.model('Test', { name: String });
const cursor = MyModel.
@@ -1183,8 +1169,6 @@ describe('aggregate: ', function() {
cursor({ useMongooseAggCursor: true }).
exec();
assert.ok(cursor instanceof require('stream').Readable);
-
- done();
});
it('cursor() with useMongooseAggCursor works (gh-5145) (gh-5394)', function(done) {
diff --git a/test/browser.test.js b/test/browser.test.js
index fb0a5d260bc..d60868ae276 100644
--- a/test/browser.test.js
+++ b/test/browser.test.js
@@ -22,16 +22,16 @@ describe('browser', function() {
exec('node --eval "const mongoose = require(\'./lib/browser\'); new mongoose.Schema();"', done);
});
- it('document works (gh-4987)', function(done) {
+ it('document works (gh-4987)', function() {
const schema = new Schema({
name: { type: String, required: true },
quest: { type: String, match: /Holy Grail/i, required: true },
favoriteColor: { type: String, enum: ['Red', 'Blue'], required: true }
});
- new Document({}, schema);
-
- done();
+ assert.doesNotThrow(function() {
+ new Document({}, schema);
+ });
});
it('document validation with arrays (gh-6175)', function() {
diff --git a/test/cast.test.js b/test/cast.test.js
index 6adfa681b17..a5dff289806 100644
--- a/test/cast.test.js
+++ b/test/cast.test.js
@@ -14,51 +14,45 @@ const Buffer = require('safe-buffer').Buffer;
describe('cast: ', function() {
describe('when casting an array', function() {
- it('casts array with ObjectIds to $in query', function(done) {
+ it('casts array with ObjectIds to $in query', function() {
const schema = new Schema({ x: Schema.Types.ObjectId });
const ids = [new ObjectId(), new ObjectId()];
assert.deepEqual(cast(schema, { x: ids }), { x: { $in: ids } });
- done();
});
- it('casts array with ObjectIds to $in query when values are strings', function(done) {
+ it('casts array with ObjectIds to $in query when values are strings', function() {
const schema = new Schema({ x: Schema.Types.ObjectId });
const ids = [new ObjectId(), new ObjectId()];
assert.deepEqual(cast(schema, { x: ids.map(String) }), { x: { $in: ids } });
- done();
});
- it('throws when ObjectIds not valid', function(done) {
+ it('throws when ObjectIds not valid', function() {
const schema = new Schema({ x: Schema.Types.ObjectId });
const ids = [123, 456, 'asfds'];
assert.throws(function() {
cast(schema, { x: ids });
}, /Cast to ObjectId failed/);
- done();
});
- it('casts array with Strings to $in query', function(done) {
+ it('casts array with Strings to $in query', function() {
const schema = new Schema({ x: String });
const strings = ['bleep', 'bloop'];
assert.deepEqual(cast(schema, { x: strings }), { x: { $in: strings } });
- done();
});
- it('casts array with Strings when necessary', function(done) {
+ it('casts array with Strings when necessary', function() {
const schema = new Schema({ x: String });
const strings = [123, 456];
assert.deepEqual(cast(schema, { x: strings }), { x: { $in: strings.map(String) } });
- done();
});
- it('casts array with Numbers to $in query', function(done) {
+ it('casts array with Numbers to $in query', function() {
const schema = new Schema({ x: Number });
const numbers = [42, 25];
assert.deepEqual(cast(schema, { x: numbers }), { x: { $in: numbers } });
- done();
});
- it('casts $in and $nin with empty array (gh-5913) (gh-7806)', function(done) {
+ it('casts $in and $nin with empty array (gh-5913) (gh-7806)', function() {
const schema = new Schema({
v: Number,
arr: [Number]
@@ -72,55 +66,47 @@ describe('cast: ', function() {
{ v: { $nin: [1, []] } });
assert.deepEqual(cast(schema, { arr: { $nin: [1, []] } }),
{ arr: { $nin: [1, []] } });
-
- done();
});
- it('casts array with Numbers to $in query when values are strings', function(done) {
+ it('casts array with Numbers to $in query when values are strings', function() {
const schema = new Schema({ x: Number });
const numbers = ['42', '25'];
assert.deepEqual(cast(schema, { x: numbers }), { x: { $in: numbers.map(Number) } });
- done();
});
- it('throws when Numbers are not valid', function(done) {
+ it('throws when Numbers are not valid', function() {
const schema = new Schema({ x: Number });
const numbers = [123, 456, 'asfds'];
assert.throws(function() {
cast(schema, { x: numbers });
}, /Cast to Number failed for value "asfds"/);
- done();
});
});
describe('bitwise query operators: ', function() {
- it('with a number', function(done) {
+ it('with a number', function() {
const schema = new Schema({ x: Buffer });
assert.deepEqual(cast(schema, { x: { $bitsAllClear: 3 } }),
{ x: { $bitsAllClear: 3 } });
- done();
});
- it('with an array', function(done) {
+ it('with an array', function() {
const schema = new Schema({ x: Buffer });
assert.deepEqual(cast(schema, { x: { $bitsAllSet: [2, '3'] } }),
{ x: { $bitsAllSet: [2, 3] } });
- done();
});
- it('with a buffer', function(done) {
+ it('with a buffer', function() {
const schema = new Schema({ x: Number });
assert.deepEqual(cast(schema, { x: { $bitsAnyClear: Buffer.from([3]) } }),
{ x: { $bitsAnyClear: Buffer.from([3]) } });
- done();
});
- it('throws when invalid', function(done) {
+ it('throws when invalid', function() {
const schema = new Schema({ x: Number });
assert.throws(function() {
cast(schema, { x: { $bitsAnySet: 'Not a number' } });
}, /Cast to number failed/);
- done();
});
});
});
diff --git a/test/collection.capped.test.js b/test/collection.capped.test.js
index 330593063cc..a0270fa47a2 100644
--- a/test/collection.capped.test.js
+++ b/test/collection.capped.test.js
@@ -28,14 +28,14 @@ describe('collections: capped:', function() {
db.close(done);
});
- it('schemas should have option size', function(done) {
+ it('schemas should have option size', function() {
const capped = new Schema({ key: String });
capped.set('capped', { size: 1000 });
assert.ok(capped.options.capped);
assert.equal(capped.options.capped.size, 1000);
- done();
});
+
it('creation', function() {
this.timeout(30000);
diff --git a/test/collection.test.js b/test/collection.test.js
index 79ac6adf822..1afb5741b63 100644
--- a/test/collection.test.js
+++ b/test/collection.test.js
@@ -59,7 +59,7 @@ describe('collections:', function() {
});
});
- it('methods should that throw (unimplemented)', function(done) {
+ it('methods should that throw (unimplemented)', function() {
const collection = new Collection('test', mongoose.connection);
let thrown = false;
@@ -142,6 +142,5 @@ describe('collections:', function() {
assert.ok(thrown);
thrown = false;
- done();
});
});
diff --git a/test/connection.test.js b/test/connection.test.js
index 152e3142781..f259e6d590f 100644
--- a/test/connection.test.js
+++ b/test/connection.test.js
@@ -119,18 +119,16 @@ describe('connections:', function() {
conn.then(() => done(), err => done(err));
});
- it('throws helpful error with legacy syntax (gh-6756)', function(done) {
+ it('throws helpful error with legacy syntax (gh-6756)', function() {
assert.throws(function() {
mongoose.createConnection('localhost', 'dbname', 27017);
}, /mongoosejs\.com.*connections\.html/);
- done();
});
- it('throws helpful error with undefined uri (gh-6763)', function(done) {
+ it('throws helpful error with undefined uri (gh-6763)', function() {
assert.throws(function() {
mongoose.createConnection(void 0, { useNewUrlParser: true });
}, /string.*createConnection/);
- done();
});
it('resolving with q (gh-5714)', function(done) {
@@ -449,7 +447,7 @@ describe('connections:', function() {
assert.equal(db.port, 27017);
});
- it('should accept unix domain sockets', function(done) {
+ it('should accept unix domain sockets', function() {
const host = encodeURIComponent('/tmp/mongodb-27017.sock');
const db = mongoose.createConnection(`mongodb://aaron:psw@${host}/fake`, { useNewUrlParser: true });
db.catch(() => {});
@@ -458,7 +456,6 @@ describe('connections:', function() {
assert.equal(db.pass, 'psw');
assert.equal(db.user, 'aaron');
db.close();
- done();
});
describe('errors', function() {
@@ -557,7 +554,7 @@ describe('connections:', function() {
db.deleteModel(/.*/);
});
- it('allows passing a schema', function(done) {
+ it('allows passing a schema', function() {
const MyModel = mongoose.model('Test', new Schema({
name: String
}));
@@ -567,48 +564,40 @@ describe('connections:', function() {
const m = new MyModel({ name: 'aaron' });
assert.equal(m.name, 'aaron');
- done();
});
- it('should properly assign the db', function(done) {
+ it('should properly assign the db', function() {
const A = mongoose.model('testing853a', new Schema({ x: String }), 'testing853-1');
const B = mongoose.model('testing853b', new Schema({ x: String }), 'testing853-2');
const C = B.model('testing853a');
assert.ok(C === A);
- done();
});
- it('prevents overwriting pre-existing models', function(done) {
+ it('prevents overwriting pre-existing models', function() {
db.deleteModel(/Test/);
db.model('Test', new Schema);
assert.throws(function() {
db.model('Test', new Schema);
}, /Cannot overwrite `Test` model/);
-
- done();
});
- it('allows passing identical name + schema args', function(done) {
+ it('allows passing identical name + schema args', function() {
const name = 'Test';
const schema = new Schema;
db.deleteModel(/Test/);
const model = db.model(name, schema);
db.model(name, model.schema);
-
- done();
});
- it('throws on unknown model name', function(done) {
+ it('throws on unknown model name', function() {
assert.throws(function() {
db.model('iDoNotExist!');
}, /Schema hasn't been registered/);
-
- done();
});
- it('uses the passed schema when global model exists with same name (gh-1209)', function(done) {
+ it('uses the passed schema when global model exists with same name (gh-1209)', function() {
const s1 = new Schema({ one: String });
const s2 = new Schema({ two: Number });
@@ -630,22 +619,20 @@ describe('connections:', function() {
assert.ok(C.schema === A.schema);
db.close();
- done();
});
describe('get existing model with not existing collection in db', function() {
- it('must return exiting collection with all collection options', function(done) {
+ it('must return exiting collection with all collection options', function() {
mongoose.model('some-th-1458', new Schema({ test: String }, { capped: { size: 1000, max: 10 } }));
const m = db.model('some-th-1458');
assert.equal(1000, m.collection.opts.capped.size);
assert.equal(10, m.collection.opts.capped.max);
- done();
});
});
describe('passing collection name', function() {
describe('when model name already exists', function() {
- it('returns a new uncached model', function(done) {
+ it('returns a new uncached model', function() {
const s1 = new Schema({ a: [] });
const name = 'Test';
const A = db.model(name, s1);
@@ -655,7 +642,6 @@ describe('connections:', function() {
assert.ok(A.collection.name !== C.collection.name);
assert.ok(db.models[name].collection.name !== C.collection.name);
assert.ok(db.models[name].collection.name === A.collection.name);
- done();
});
});
});
@@ -1040,32 +1026,29 @@ describe('connections:', function() {
describe('shouldAuthenticate()', function() {
describe('when using standard authentication', function() {
describe('when username and password are undefined', function() {
- it('should return false', function(done) {
+ it('should return false', function() {
const db = mongoose.createConnection('mongodb://localhost:27017/fake', {});
assert.equal(db.shouldAuthenticate(), false);
db.close();
- done();
});
});
describe('when username and password are empty strings', function() {
- it('should return false', function(done) {
+ it('should return false', function() {
const db = mongoose.createConnection('mongodb://localhost:27017/fake', {
user: '',
pass: ''
});
- db.on('error', function() {
- });
+ db.on('error', function() {});
assert.equal(db.shouldAuthenticate(), false);
db.close();
- done();
});
});
describe('when both username and password are defined', function() {
- it('should return true', function(done) {
+ it('should return true', function() {
const db = mongoose.createConnection('mongodb://localhost:27017/fake', {
user: 'user',
pass: 'pass'
@@ -1075,13 +1058,12 @@ describe('connections:', function() {
assert.equal(db.shouldAuthenticate(), true);
db.close();
- done();
});
});
});
describe('when using MONGODB-X509 authentication', function() {
describe('when username and password are undefined', function() {
- it('should return false', function(done) {
+ it('should return false', function() {
const db = mongoose.createConnection('mongodb://localhost:27017/fake', {});
db.on('error', function() {
});
@@ -1089,11 +1071,10 @@ describe('connections:', function() {
assert.equal(db.shouldAuthenticate(), false);
db.close();
- done();
});
});
describe('when only username is defined', function() {
- it('should return false', function(done) {
+ it('should return false', function() {
const db = mongoose.createConnection('mongodb://localhost:27017/fake', {
user: 'user',
auth: { authMechanism: 'MONGODB-X509' }
@@ -1102,11 +1083,10 @@ describe('connections:', function() {
assert.equal(db.shouldAuthenticate(), true);
db.close();
- done();
});
});
describe('when both username and password are defined', function() {
- it('should return false', function(done) {
+ it('should return false', function() {
const db = mongoose.createConnection('mongodb://localhost:27017/fake', {
user: 'user',
pass: 'pass',
@@ -1117,20 +1097,18 @@ describe('connections:', function() {
assert.equal(db.shouldAuthenticate(), true);
db.close();
- done();
});
});
});
});
describe('passing a function into createConnection', function() {
- it('should store the name of the function (gh-6517)', function(done) {
+ it('should store the name of the function (gh-6517)', function() {
const conn = mongoose.createConnection('mongodb://localhost:27017/gh6517');
const schema = new Schema({ name: String });
class Person extends mongoose.Model {}
conn.model(Person, schema);
assert.strictEqual(conn.modelNames()[0], 'Person');
- done();
});
});
diff --git a/test/docs/transactions.test.js b/test/docs/transactions.test.js
index e27662861d8..73daab51e83 100644
--- a/test/docs/transactions.test.js
+++ b/test/docs/transactions.test.js
@@ -360,7 +360,26 @@ describe('transactions', function() {
const test = yield Test.create([{}], { session }).then(res => res[0]);
yield test.save(); // throws DocumentNotFoundError
}));
- yield session.endSession();
+ session.endSession();
+ });
+ });
+
+ it('correct `isNew` after abort (gh-8852)', function() {
+ return co(function*() {
+ const schema = Schema({ name: String });
+
+ const Test = db.model('gh8852', schema);
+
+ yield Test.createCollection();
+ const doc = new Test({ name: 'foo' });
+ yield db.
+ transaction(session => co(function*() {
+ yield doc.save({ session });
+ assert.ok(!doc.isNew);
+ throw new Error('Oops');
+ })).
+ catch(err => assert.equal(err.message, 'Oops'));
+ assert.ok(doc.isNew);
});
});
});
diff --git a/test/document.isselected.test.js b/test/document.isselected.test.js
index ef199240a6a..70d4abb9277 100644
--- a/test/document.isselected.test.js
+++ b/test/document.isselected.test.js
@@ -97,7 +97,7 @@ TestDocument.prototype.hooksTest = function(fn) {
* Test.
*/
describe('document', function() {
- it('isSelected()', function(done) {
+ it('isSelected()', function() {
let doc = new TestDocument();
doc.init({
@@ -332,11 +332,9 @@ describe('document', function() {
assert.ok(!doc.isSelected('nested'));
assert.ok(!doc.isSelected('nested.age'));
assert.ok(!doc.isSelected('numbers'));
-
- done();
});
- it('isDirectSelected (gh-5063)', function(done) {
+ it('isDirectSelected (gh-5063)', function() {
const selection = {
test: 1,
numbers: 1,
@@ -357,7 +355,5 @@ describe('document', function() {
assert.ok(doc.isDirectSelected('nested.deep'));
assert.ok(!doc.isDirectSelected('nested.cool'));
assert.ok(!doc.isDirectSelected('nested'));
-
- done();
});
});
diff --git a/test/document.modified.test.js b/test/document.modified.test.js
index 0d344d8d61c..1e5336df3fa 100644
--- a/test/document.modified.test.js
+++ b/test/document.modified.test.js
@@ -123,31 +123,24 @@ describe('document modified', function() {
});
describe('isDefault', function() {
- it('works', function(done) {
+ it('works', function() {
const MyModel = db.model('Test',
{ name: { type: String, default: 'Val ' } });
const m = new MyModel();
assert.ok(m.$isDefault('name'));
- done();
});
});
describe('isModified', function() {
- it('should not throw with no argument', function(done) {
+ it('should not throw with no argument', function() {
const post = new BlogPost;
- let threw = false;
- try {
+ assert.doesNotThrow(function() {
post.isModified();
- } catch (err) {
- threw = true;
- }
-
- assert.equal(threw, false);
- done();
+ });
});
- it('when modifying keys', function(done) {
+ it('when modifying keys', function() {
const post = new BlogPost;
post.init({
title: 'Test',
@@ -164,10 +157,9 @@ describe('document modified', function() {
assert.equal(post.isModified('date'), true);
assert.equal(post.isModified('meta.date'), false);
- done();
});
- it('setting a key identically to its current value should not dirty the key', function(done) {
+ it('setting a key identically to its current value should not dirty the key', function() {
const post = new BlogPost;
post.init({
title: 'Test',
@@ -178,11 +170,10 @@ describe('document modified', function() {
assert.equal(post.isModified('title'), false);
post.set('title', 'Test');
assert.equal(post.isModified('title'), false);
- done();
});
describe('on DocumentArray', function() {
- it('work', function(done) {
+ it('work', function() {
const post = new BlogPost();
post.init({
title: 'Test',
@@ -196,10 +187,8 @@ describe('document modified', function() {
assert.equal(post.isDirectModified('comments'), false);
assert.equal(post.isModified('comments.0.title'), true);
assert.equal(post.isDirectModified('comments.0.title'), true);
-
- done();
});
- it('with accessors', function(done) {
+ it('with accessors', function() {
const post = new BlogPost();
post.init({
title: 'Test',
@@ -213,23 +202,19 @@ describe('document modified', function() {
assert.equal(post.isDirectModified('comments'), false);
assert.equal(post.isModified('comments.0.body'), true);
assert.equal(post.isDirectModified('comments.0.body'), true);
-
- done();
});
});
describe('on MongooseArray', function() {
- it('atomic methods', function(done) {
+ it('atomic methods', function() {
const post = new BlogPost();
assert.equal(post.isModified('owners'), false);
post.get('owners').push(new DocumentObjectId);
assert.equal(post.isModified('owners'), true);
- done();
});
- it('native methods', function(done) {
+ it('native methods', function() {
const post = new BlogPost;
assert.equal(post.isModified('owners'), false);
- done();
});
});
@@ -335,7 +320,7 @@ describe('document modified', function() {
});
});
- it('properly sets populated for gh-1530 (gh-2678)', function(done) {
+ it('properly sets populated for gh-1530 (gh-2678)', function() {
const parentSchema = new Schema({
name: String,
child: { type: Schema.Types.ObjectId, ref: 'Child' }
@@ -348,7 +333,6 @@ describe('document modified', function() {
const p = new Parent({ name: 'Alex', child: child });
assert.equal(child._id.toString(), p.populated('child').toString());
- done();
});
describe('manually populating arrays', function() {
@@ -362,7 +346,7 @@ describe('document modified', function() {
db.close(done);
});
- it('gh-1530 for arrays (gh-3575)', function(done) {
+ it('gh-1530 for arrays (gh-3575)', function() {
const parentSchema = new Schema({
name: String,
children: [{ type: Schema.Types.ObjectId, ref: 'Child' }]
@@ -376,7 +360,6 @@ describe('document modified', function() {
assert.equal('Luke', p.children[0].name);
assert.ok(p.populated('children'));
- done();
});
it('setting nested arrays (gh-3721)', function() {
@@ -405,7 +388,7 @@ describe('document modified', function() {
return Promise.all([User.init(), Account.init()]);
});
- it('with discriminators (gh-3575)', function(done) {
+ it('with discriminators (gh-3575)', function() {
const shapeSchema = new mongoose.Schema({}, { discriminatorKey: 'kind' });
db.deleteModel(/Test/);
@@ -430,8 +413,6 @@ describe('document modified', function() {
assert.ok(test.populated('bars'));
assert.ok(test.bars[0]._id);
assert.ok(test.bars[1]._id);
-
- done();
});
it('updates embedded doc parents upon direct assignment (gh-5189)', function(done) {
diff --git a/test/document.populate.test.js b/test/document.populate.test.js
index 3873320cee1..4e7c7e3930b 100644
--- a/test/document.populate.test.js
+++ b/test/document.populate.test.js
@@ -200,11 +200,10 @@ describe('document.populate', function() {
});
});
- it('are not modified when no arguments are passed', function(done) {
+ it('are not modified when no arguments are passed', function() {
const d = new TestDocument();
const o = utils.clone(d.options);
assert.deepEqual(o, d.populate().options);
- done();
});
});
@@ -498,7 +497,7 @@ describe('document.populate', function() {
});
});
- it('depopulates when setting `_id` (gh-3308)', function(done) {
+ it('depopulates when setting `_id` (gh-3308)', function() {
const Person = db.model('Person', {
name: String
});
@@ -517,8 +516,6 @@ describe('document.populate', function() {
const buckethead = new Person({ name: 'Buckethead' });
gnr.guitarist = buckethead._id;
assert.ok(!gnr.populated('guitarist'));
-
- done();
});
describe('gh-2214', function() {
@@ -799,7 +796,7 @@ describe('document.populate', function() {
});
});
- it('does not allow you to call populate() on nested docs (gh-4552)', function(done) {
+ it('does not allow you to call populate() on nested docs (gh-4552)', function() {
const EmbeddedSchema = new Schema({
reference: {
type: mongoose.Schema.ObjectId,
@@ -818,7 +815,6 @@ describe('document.populate', function() {
assert.throws(function() {
m.embedded.populate('reference');
}, /on nested docs/);
- done();
});
it('handles pulling from populated array (gh-3579)', function(done) {
diff --git a/test/document.strict.test.js b/test/document.strict.test.js
index 2a98ec12072..f6401010227 100644
--- a/test/document.strict.test.js
+++ b/test/document.strict.test.js
@@ -73,7 +73,7 @@ describe('document: strict mode:', function() {
});
});
- it('when creating models with strict schemas', function(done) {
+ it('when creating models with strict schemas', function() {
const s = new Strict({ content: 'sample', rouge: 'data' });
assert.equal(s.$__.strictMode, true);
@@ -86,10 +86,9 @@ describe('document: strict mode:', function() {
assert.ok(!('rouge' in so));
assert.ok(!s.rouge);
assert.ok(!so.rouge);
- done();
});
- it('when overriding strictness', function(done) {
+ it('when overriding strictness', function() {
// instance override
let instance = new Lax({ content: 'sample', rouge: 'data' }, true);
assert.ok(instance.$__.strictMode);
@@ -114,7 +113,6 @@ describe('document: strict mode:', function() {
assert.equal(s3.content, 'sample');
assert.ok(!('rouge' in s3));
assert.ok(!s3.rouge);
- done();
});
it('when using Model#create', function(done) {
@@ -128,7 +126,7 @@ describe('document: strict mode:', function() {
});
});
- it('nested doc', function(done) {
+ it('nested doc', function() {
const lax = new Schema({
name: { last: String }
}, { strict: false });
@@ -161,7 +159,6 @@ describe('document: strict mode:', function() {
assert.ok(!('hack' in s.name));
assert.ok(!s.name.hack);
assert.ok(!s.shouldnt);
- done();
});
it('sub doc', function(done) {
@@ -210,7 +207,7 @@ describe('document: strict mode:', function() {
});
});
- it('virtuals', function(done) {
+ it('virtuals', function() {
let getCount = 0,
setCount = 0;
@@ -248,8 +245,6 @@ describe('document: strict mode:', function() {
assert.equal('string', typeof temp);
assert.equal(getCount, 1);
assert.equal(setCount, 2);
-
- done();
});
it('can be overridden during set()', function(done) {
@@ -355,7 +350,7 @@ describe('document: strict mode:', function() {
});
describe('"throws" mode', function() {
- it('throws on set() of unknown property', function(done) {
+ it('throws on set() of unknown property', function() {
const schema = new Schema({ n: String, docs: [{ x: [{ y: String }] }] });
schema.set('strict', 'throw');
const M = db.model('Test', schema);
@@ -398,11 +393,9 @@ describe('document: strict mode:', function() {
assert.throws(function() {
m.set('docs.0.x.4.y.z', 3);
}, badField);
-
- done();
});
- it('fails with extra fields', function(done) {
+ it('fails with extra fields', function() {
// Simple schema with throws option
const FooSchema = new mongoose.Schema({
name: { type: String }
@@ -419,11 +412,9 @@ describe('document: strict mode:', function() {
// The extra baz field should throw
new Foo({ name: 'bar', baz: 'bam' });
}, /Field `baz` is not in schema/);
-
- done();
});
- it('doesnt throw with refs (gh-2665)', function(done) {
+ it('doesnt throw with refs (gh-2665)', function() {
// Simple schema with throws option
const FooSchema = new mongoose.Schema({
name: { type: mongoose.Schema.Types.ObjectId, ref: 'test', required: false, default: null },
@@ -436,11 +427,9 @@ describe('document: strict mode:', function() {
assert.doesNotThrow(function() {
new Foo({ name: mongoose.Types.ObjectId(), father: { name: { full: 'bacon' } } });
});
-
- done();
});
- it('set nested to num throws ObjectExpectedError (gh-3735)', function(done) {
+ it('set nested to num throws ObjectExpectedError (gh-3735)', function() {
const schema = new Schema({
resolved: {
by: { type: String }
@@ -452,7 +441,6 @@ describe('document: strict mode:', function() {
assert.throws(function() {
new Test({ resolved: 123 });
}, /ObjectExpectedError/);
- done();
});
});
diff --git a/test/document.test.js b/test/document.test.js
index dc4d42ef48f..0554d6810b4 100644
--- a/test/document.test.js
+++ b/test/document.test.js
@@ -219,14 +219,13 @@ describe('document', function() {
});
describe('shortcut getters', function() {
- it('return undefined for properties with a null/undefined parent object (gh-1326)', function(done) {
+ it('return undefined for properties with a null/undefined parent object (gh-1326)', function() {
const doc = new TestDocument;
doc.init({ nested: null });
assert.strictEqual(undefined, doc.nested.age);
- done();
});
- it('work', function(done) {
+ it('work', function() {
const doc = new TestDocument();
doc.init({
test: 'test',
@@ -299,11 +298,10 @@ describe('document', function() {
assert.equal(String(doc2.nested.cool), '4cf70857337498f95900001c');
assert.ok(doc.oids !== doc2.oids);
- done();
});
});
- it('test shortcut setters', function(done) {
+ it('test shortcut setters', function() {
const doc = new TestDocument();
doc.init({
@@ -328,22 +326,19 @@ describe('document', function() {
assert.equal(Object.keys(doc._doc.nested).length, 1);
assert.equal(doc.nested.path, 'overwrite the entire nested object');
assert.ok(doc.isModified('nested'));
- done();
});
- it('test accessor of id', function(done) {
+ it('test accessor of id', function() {
const doc = new TestDocument();
assert.ok(doc._id instanceof DocumentObjectId);
- done();
});
- it('test shortcut of id hexString', function(done) {
+ it('test shortcut of id hexString', function() {
const doc = new TestDocument();
assert.equal(typeof doc.id, 'string');
- done();
});
- it('toObject options', function(done) {
+ it('toObject options', function() {
const doc = new TestDocument();
doc.init({
@@ -498,7 +493,6 @@ describe('document', function() {
// all done
delete doc.schema.options.toObject;
- done();
});
it('toObject transform', function(done) {
@@ -700,7 +694,7 @@ describe('document', function() {
});
});
- it('handles child schema transforms', function(done) {
+ it('handles child schema transforms', function() {
const userSchema = new Schema({
name: String,
email: String
@@ -736,7 +730,6 @@ describe('document', function() {
assert.equal(output.email, 'a@b.co');
assert.equal(output.followers[0].name, 'Val');
assert.equal(output.followers[0].email, undefined);
- done();
});
it('doesnt clobber child schema options when called with no params (gh-2035)', function(done) {
@@ -784,7 +777,7 @@ describe('document', function() {
});
describe('toJSON', function() {
- it('toJSON options', function(done) {
+ it('toJSON options', function() {
const doc = new TestDocument();
doc.init({
@@ -910,10 +903,9 @@ describe('document', function() {
// all done
delete doc.schema.options.toJSON;
- done();
});
- it('jsonifying an object', function(done) {
+ it('jsonifying an object', function() {
const doc = new TestDocument({ test: 'woot' });
const oidString = doc._id.toString();
// convert to json string
@@ -923,7 +915,6 @@ describe('document', function() {
assert.equal(obj.test, 'woot');
assert.equal(obj._id, oidString);
- done();
});
it('jsonifying an object\'s populated items works (gh-1376)', function(done) {
@@ -1067,12 +1058,11 @@ describe('document', function() {
});
describe.skip('#update', function() {
- it('returns a Query', function(done) {
+ it('returns a Query', function() {
const mg = new mongoose.Mongoose;
const M = mg.model('Test', { s: String });
const doc = new M;
assert.ok(doc.update() instanceof Query);
- done();
});
it('calling update on document should relay to its model (gh-794)', function(done) {
const Docs = new Schema({ text: String });
@@ -1100,31 +1090,28 @@ describe('document', function() {
});
});
- it('toObject should not set undefined values to null', function(done) {
+ it('toObject should not set undefined values to null', function() {
const doc = new TestDocument();
const obj = doc.toObject();
delete obj._id;
assert.deepEqual(obj, { numbers: [], oids: [], em: [] });
- done();
});
describe('Errors', function() {
- it('MongooseErrors should be instances of Error (gh-209)', function(done) {
+ it('MongooseErrors should be instances of Error (gh-209)', function() {
const MongooseError = require('../lib/error');
const err = new MongooseError('Some message');
assert.ok(err instanceof Error);
- done();
});
- it('ValidationErrors should be instances of Error', function(done) {
+ it('ValidationErrors should be instances of Error', function() {
const ValidationError = Document.ValidationError;
const err = new ValidationError(new TestDocument);
assert.ok(err instanceof Error);
- done();
});
});
- it('methods on embedded docs should work', function(done) {
+ it('methods on embedded docs should work', function() {
const ESchema = new Schema({ name: String });
ESchema.methods.test = function() {
@@ -1150,10 +1137,9 @@ describe('document', function() {
assert.equal(typeof p.embed[0].test, 'function');
assert.equal(typeof E.ten, 'function');
assert.equal(p.embed[0].test(), 'apple butter');
- done();
});
- it('setting a positional path does not cast value to array', function(done) {
+ it('setting a positional path does not cast value to array', function() {
const doc = new TestDocument;
doc.init({ numbers: [1, 3] });
assert.equal(doc.numbers[0], 1);
@@ -1161,10 +1147,9 @@ describe('document', function() {
doc.set('numbers.1', 2);
assert.equal(doc.numbers[0], 1);
assert.equal(doc.numbers[1], 2);
- done();
});
- it('no maxListeners warning should occur', function(done) {
+ it('no maxListeners warning should occur', function() {
let traced = false;
const trace = console.trace;
@@ -1192,7 +1177,6 @@ describe('document', function() {
new S({ title: 'test' });
assert.equal(traced, false);
- done();
});
it('unselected required fields should pass validation', function(done) {
@@ -1601,19 +1585,17 @@ describe('document', function() {
M = db.model('Test5', new Schema({ name: String }, { _id: false }));
});
- it('with string _ids', function(done) {
+ it('with string _ids', function() {
const s1 = new S({ _id: 'one' });
const s2 = new S({ _id: 'one' });
assert.ok(s1.equals(s2));
- done();
});
- it('with number _ids', function(done) {
+ it('with number _ids', function() {
const n1 = new N({ _id: 0 });
const n2 = new N({ _id: 0 });
assert.ok(n1.equals(n2));
- done();
});
- it('with ObjectId _ids', function(done) {
+ it('with ObjectId _ids', function() {
let id = new mongoose.Types.ObjectId;
let o1 = new O({ _id: id });
let o2 = new O({ _id: id });
@@ -1623,28 +1605,25 @@ describe('document', function() {
o1 = new O({ _id: id });
o2 = new O({ _id: id });
assert.ok(o1.equals(o2));
- done();
});
- it('with Buffer _ids', function(done) {
+ it('with Buffer _ids', function() {
const n1 = new B({ _id: 0 });
const n2 = new B({ _id: 0 });
assert.ok(n1.equals(n2));
- done();
});
- it('with _id disabled (gh-1687)', function(done) {
+ it('with _id disabled (gh-1687)', function() {
const m1 = new M;
const m2 = new M;
assert.doesNotThrow(function() {
m1.equals(m2);
});
- done();
});
});
});
describe('setter', function() {
describe('order', function() {
- it('is applied correctly', function(done) {
+ it('is applied correctly', function() {
const date = 'Thu Aug 16 2012 09:45:59 GMT-0700';
const d = new TestDocument();
dateSetterCalled = false;
@@ -1654,7 +1633,6 @@ describe('document', function() {
assert.ok(d._doc.date instanceof Date);
assert.ok(d.date instanceof Date);
assert.equal(+d.date, +new Date(date));
- done();
});
});
@@ -1673,7 +1651,7 @@ describe('document', function() {
describe('on nested paths', function() {
describe('using set(path, object)', function() {
- it('overwrites the entire object', function(done) {
+ it('overwrites the entire object', function() {
let doc = new TestDocument();
doc.init({
@@ -1755,8 +1733,6 @@ describe('document', function() {
assert.ok(!doc.isModified('nested.age'));
assert.ok(doc.isModified('nested.deep'));
assert.equal(doc.nested.deep.x, 'Hank and Marie');
-
- done();
});
it('allows positional syntax on mixed nested paths (gh-6738)', function() {
@@ -1772,7 +1748,7 @@ describe('document', function() {
assert.strictEqual(doc.nested.a.b.c.d.e.f, 'g');
});
- it('gh-1954', function(done) {
+ it('gh-1954', function() {
const schema = new Schema({
schedule: [new Schema({ open: Number, close: Number })]
});
@@ -1793,19 +1769,16 @@ describe('document', function() {
assert.ok(doc.schedule[0] instanceof EmbeddedDocument);
assert.equal(doc.schedule[0].open, 1100);
assert.equal(doc.schedule[0].close, 1900);
-
- done();
});
});
describe('when overwriting with a document instance', function() {
- it('does not cause StackOverflows (gh-1234)', function(done) {
+ it('does not cause StackOverflows (gh-1234)', function() {
const doc = new TestDocument({ nested: { age: 35 } });
doc.nested = doc.nested;
assert.doesNotThrow(function() {
doc.nested.age;
});
- done();
});
});
});
@@ -1816,7 +1789,7 @@ describe('document', function() {
let val;
let M;
- beforeEach(function(done) {
+ beforeEach(function() {
const schema = new mongoose.Schema({ v: Number });
schema.virtual('thang').set(function(v) {
val = v;
@@ -1824,28 +1797,23 @@ describe('document', function() {
db.deleteModel(/Test/);
M = db.model('Test', schema);
- done();
});
- it('works with objects', function(done) {
+ it('works with objects', function() {
new M({ thang: {} });
assert.deepEqual({}, val);
- done();
});
- it('works with arrays', function(done) {
+ it('works with arrays', function() {
new M({ thang: [] });
assert.deepEqual([], val);
- done();
});
- it('works with numbers', function(done) {
+ it('works with numbers', function() {
new M({ thang: 4 });
assert.deepEqual(4, val);
- done();
});
- it('works with strings', function(done) {
+ it('works with strings', function() {
new M({ thang: '3' });
assert.deepEqual('3', val);
- done();
});
});
@@ -2100,7 +2068,7 @@ describe('document', function() {
});
});
- it('properly calls queue functions (gh-2856)', function(done) {
+ it('properly calls queue functions (gh-2856)', function() {
const personSchema = new mongoose.Schema({
name: String
});
@@ -2114,7 +2082,6 @@ describe('document', function() {
const Person = db.model('Person', personSchema);
new Person({ name: 'Val' });
assert.equal(calledName, 'Val');
- done();
});
describe('bug fixes', function() {
@@ -2206,7 +2173,7 @@ describe('document', function() {
});
});
- it('setters firing with objects on real paths (gh-2943)', function(done) {
+ it('setters firing with objects on real paths (gh-2943)', function() {
const M = db.model('Test', {
myStr: {
type: String, set: function(v) {
@@ -2221,12 +2188,10 @@ describe('document', function() {
new M({ otherStr: { value: 'test' } });
assert.ok(!t.otherStr);
-
- done();
});
describe('gh-2782', function() {
- it('should set data from a sub doc', function(done) {
+ it('should set data from a sub doc', function() {
const schema1 = new mongoose.Schema({
data: {
email: String
@@ -2243,11 +2208,10 @@ describe('document', function() {
const doc2 = new Model2();
doc2.set(doc1.data);
assert.equal(doc2.email, 'some@example.com');
- done();
});
});
- it('set data from subdoc keys (gh-3346)', function(done) {
+ it('set data from subdoc keys (gh-3346)', function() {
const schema1 = new mongoose.Schema({
data: {
email: String
@@ -2259,7 +2223,6 @@ describe('document', function() {
assert.equal(doc1.data.email, 'some@example.com');
const doc2 = new Model1({ data: doc1.data });
assert.equal(doc2.data.email, 'some@example.com');
- done();
});
it('doesnt attempt to cast generic objects as strings (gh-3030)', function(done) {
@@ -2322,7 +2285,7 @@ describe('document', function() {
});
});
- it('single embedded schemas with validation (gh-2689)', function(done) {
+ it('single embedded schemas with validation (gh-2689)', function() {
const userSchema = new mongoose.Schema({
name: String,
email: { type: String, required: true, match: /.+@.+/ }
@@ -2347,11 +2310,9 @@ describe('document', function() {
assert.ok(error);
assert.ok(error.errors['user.email']);
assert.equal(error.errors['user.email'].kind, 'regexp');
-
- done();
});
- it('single embedded parent() (gh-5134)', function(done) {
+ it('single embedded parent() (gh-5134)', function() {
const userSchema = new mongoose.Schema({
name: String,
email: { type: String, required: true, match: /.+@.+/ }
@@ -2366,8 +2327,6 @@ describe('document', function() {
const e = new Event({ name: 'test', user: {} });
assert.strictEqual(e.user.parent(), e.user.ownerDocument());
-
- done();
});
it('single embedded schemas with markmodified (gh-2689)', function(done) {
@@ -2644,7 +2603,7 @@ describe('document', function() {
});
});
- it('single embedded schemas with methods (gh-3534)', function(done) {
+ it('single embedded schemas with methods (gh-3534)', function() {
const personSchema = new Schema({ name: String });
personSchema.methods.firstName = function() {
return this.name.substr(0, this.name.indexOf(' '));
@@ -2655,7 +2614,6 @@ describe('document', function() {
const gnr = new Band({ leadSinger: { name: 'Axl Rose' } });
assert.equal(gnr.leadSinger.firstName(), 'Axl');
- done();
});
it('single embedded schemas with models (gh-3535)', function(done) {
@@ -2675,7 +2633,7 @@ describe('document', function() {
});
});
- it('single embedded schemas with indexes (gh-3594)', function(done) {
+ it('single embedded schemas with indexes (gh-3594)', function() {
const personSchema = new Schema({ name: { type: String, unique: true } });
const bandSchema = new Schema({ leadSinger: personSchema });
@@ -2684,7 +2642,6 @@ describe('document', function() {
const index = bandSchema.indexes()[0];
assert.deepEqual(index[0], { 'leadSinger.name': 1 });
assert.ok(index[1].unique);
- done();
});
it('removing single embedded docs (gh-3596)', function(done) {
@@ -2836,7 +2793,7 @@ describe('document', function() {
});
});
- it('handles virtuals with dots correctly (gh-3618)', function(done) {
+ it('handles virtuals with dots correctly (gh-3618)', function() {
const testSchema = new Schema({ nested: { type: Object, default: {} } });
testSchema.virtual('nested.test').get(function() {
return true;
@@ -2855,7 +2812,6 @@ describe('document', function() {
delete doc._id;
delete doc.id;
assert.deepEqual(doc, { nested: { test: true } });
- done();
});
it('handles pushing with numeric keys (gh-3623)', function(done) {
@@ -2946,7 +2902,7 @@ describe('document', function() {
});
});
- it('handles conflicting names (gh-3867)', function(done) {
+ it('handles conflicting names (gh-3867)', function() {
const testSchema = new Schema({
name: {
type: String,
@@ -2968,7 +2924,6 @@ describe('document', function() {
const fields = Object.keys(doc.validateSync().errors).sort();
assert.deepEqual(fields, ['name', 'things.0.name']);
- done();
});
it('populate with lean (gh-3873)', function(done) {
@@ -3215,7 +3170,7 @@ describe('document', function() {
});
});
- it('skip validation if required returns false (gh-4094)', function(done) {
+ it('skip validation if required returns false (gh-4094)', function() {
const schema = new Schema({
div: {
type: Number,
@@ -3226,7 +3181,6 @@ describe('document', function() {
const Model = db.model('Test', schema);
const m = new Model();
assert.ifError(m.validateSync());
- done();
});
it('ability to overwrite array default (gh-4109)', function(done) {
@@ -3314,7 +3268,7 @@ describe('document', function() {
catch(done);
});
- it('single embedded with defaults have $parent (gh-4115)', function(done) {
+ it('single embedded with defaults have $parent (gh-4115)', function() {
const ChildSchema = new Schema({
name: {
type: String,
@@ -3333,7 +3287,6 @@ describe('document', function() {
const p = new Parent();
assert.equal(p.child.$parent, p);
- done();
});
it('removing parent doc calls remove hooks on subdocs (gh-2348) (gh-4566)', function(done) {
@@ -3566,19 +3519,16 @@ describe('document', function() {
});
});
- it('minimize + empty object (gh-4337)', function(done) {
+ it('minimize + empty object (gh-4337)', function() {
const SomeModelSchema = new mongoose.Schema({}, {
minimize: false
});
const SomeModel = db.model('Test', SomeModelSchema);
- try {
+ assert.doesNotThrow(function() {
new SomeModel({});
- } catch (error) {
- assert.ifError(error);
- }
- done();
+ });
});
it('directModifiedPaths() (gh-7373)', function() {
@@ -3616,7 +3566,7 @@ describe('document', function() {
});
});
- it('includeChildren option (gh-6134)', function(done) {
+ it('includeChildren option (gh-6134)', function() {
const personSchema = new mongoose.Schema({
name: { type: String },
colors: {
@@ -3645,11 +3595,9 @@ describe('document', function() {
const anakin = new Person({ name: 'Anakin' });
anakin.colors = { primary: 'blue' };
assert.deepEqual(anakin.modifiedPaths({ includeChildren: true }), ['name', 'colors', 'colors.primary']);
-
- done();
});
- it('includeChildren option with arrays (gh-5904)', function(done) {
+ it('includeChildren option with arrays (gh-5904)', function() {
const teamSchema = new mongoose.Schema({
name: String,
colors: {
@@ -3682,8 +3630,6 @@ describe('document', function() {
'members.0',
'members.0.name'
]);
-
- done();
});
it('1 level down nested paths get marked modified on initial set (gh-7313) (gh-6944)', function() {
@@ -3745,7 +3691,7 @@ describe('document', function() {
});
});
- it('deep default array values (gh-4540)', function(done) {
+ it('deep default array values (gh-4540)', function() {
const schema = new Schema({
arr: [{
test: {
@@ -3757,7 +3703,6 @@ describe('document', function() {
assert.doesNotThrow(function() {
db.model('Test', schema);
});
- done();
});
it('default values with subdoc array (gh-4390)', function(done) {
@@ -3798,7 +3743,7 @@ describe('document', function() {
});
});
- it('setting array subpath (gh-4472)', function(done) {
+ it('setting array subpath (gh-4472)', function() {
const ChildSchema = new mongoose.Schema({
name: String,
age: Number
@@ -3819,7 +3764,6 @@ describe('document', function() {
});
assert.deepEqual(p.toObject().data.children, [{ name: 'Bob', age: 900 }]);
- done();
});
it('ignore paths (gh-4480)', function() {
@@ -3911,7 +3855,7 @@ describe('document', function() {
catch(done);
});
- it('validateSync with undefined and conditional required (gh-4607)', function(done) {
+ it('validateSync with undefined and conditional required (gh-4607)', function() {
const schema = new mongoose.Schema({
type: mongoose.SchemaTypes.Number,
conditional: {
@@ -3931,11 +3875,9 @@ describe('document', function() {
conditional: void 0
}).validateSync();
});
-
- done();
});
- it('conditional required on single nested (gh-4663)', function(done) {
+ it('conditional required on single nested (gh-4663)', function() {
const childSchema = new Schema({
name: String
});
@@ -3950,8 +3892,8 @@ describe('document', function() {
const M = db.model('Test', schema);
- new M({ child: { name: 'test' } }).validateSync();
- done();
+ const err = new M({ child: { name: 'test' } }).validateSync();
+ assert.ifError(err);
});
it('setting full path under single nested schema works (gh-4578) (gh-4528)', function(done) {
@@ -4010,7 +3952,7 @@ describe('document', function() {
});
});
- it('toObject() does not depopulate top level (gh-3057)', function(done) {
+ it('toObject() does not depopulate top level (gh-3057)', function() {
const Cat = db.model('Cat', { name: String });
const Human = db.model('Person', {
name: String,
@@ -4022,10 +3964,9 @@ describe('document', function() {
assert.equal(kitty.toObject({ depopulate: true }).name, 'Zildjian');
assert.ok(!person.toObject({ depopulate: true }).petCat.name);
- done();
});
- it('toObject() respects schema-level depopulate (gh-6313)', function(done) {
+ it('toObject() respects schema-level depopulate (gh-6313)', function() {
const personSchema = Schema({
name: String,
car: {
@@ -4055,7 +3996,6 @@ describe('document', function() {
});
assert.equal(person.toObject().car.toHexString(), car._id.toHexString());
- done();
});
it('single nested doc conditional required (gh-4654)', function(done) {
@@ -4261,7 +4201,7 @@ describe('document', function() {
catch(done);
});
- it('handles setting virtual subpaths (gh-4716)', function(done) {
+ it('handles setting virtual subpaths (gh-4716)', function() {
const childSchema = new Schema({
name: { type: String, default: 'John' },
favorites: {
@@ -4291,7 +4231,6 @@ describe('document', function() {
p.set('children.0.name', 'Leah');
p.set('favorites.color', 'Red');
assert.equal(p.children[0].favorites.color, 'Red');
- done();
});
it('handles selected nested elements with defaults (gh-4739)', function(done) {
@@ -4317,7 +4256,7 @@ describe('document', function() {
});
});
- it('handles mark valid in subdocs correctly (gh-4778)', function(done) {
+ it('handles mark valid in subdocs correctly (gh-4778)', function() {
const SubSchema = new mongoose.Schema({
field: {
nestedField: {
@@ -4342,7 +4281,6 @@ describe('document', function() {
doc.sub.field.nestedField = { };
doc.sub.field.nestedField = '574b69d0d9daf106aaa62974';
assert.ok(!doc.validateSync());
- done();
});
it('timestamps set to false works (gh-7074)', function() {
@@ -4376,7 +4314,7 @@ describe('document', function() {
});
});
- it('Declaring defaults in your schema with timestamps defined (gh-6024)', function(done) {
+ it('Declaring defaults in your schema with timestamps defined (gh-6024)', function() {
const schemaDefinition = {
name: String,
misc: {
@@ -4389,8 +4327,6 @@ describe('document', function() {
const PersonWithTimestamps = db.model('Person', schemaWithTimestamps);
const dude = new PersonWithTimestamps({ name: 'Keanu', misc: { hometown: 'Beirut' } });
assert.equal(dude.misc.isAlive, true);
-
- done();
});
it('supports $where in pre save hook (gh-4004)', function(done) {
@@ -4457,14 +4393,15 @@ describe('document', function() {
return Test.findById(doc._id);
}).
then(function(doc) {
- // Should not throw
- require('util').inspect(doc);
+ assert.doesNotThrow(function() {
+ require('util').inspect(doc);
+ });
done();
}).
catch(done);
});
- it('buffer subtype prop (gh-5530)', function(done) {
+ it('buffer subtype prop (gh-5530)', function() {
const TestSchema = new mongoose.Schema({
uuid: {
type: Buffer,
@@ -4476,7 +4413,6 @@ describe('document', function() {
const doc = new Test({ uuid: 'test1' });
assert.equal(doc.uuid._subtype, 4);
- done();
});
it('runs validate hooks on single nested subdocs if not directly modified (gh-3884)', function(done) {
@@ -4629,7 +4565,7 @@ describe('document', function() {
return Promise.resolve();
});
- it('does not overwrite when setting nested (gh-4793)', function(done) {
+ it('does not overwrite when setting nested (gh-4793)', function() {
const grandchildSchema = new mongoose.Schema();
grandchildSchema.method({
foo: function() { return 'bar'; }
@@ -4655,7 +4591,6 @@ describe('document', function() {
assert.equal(child.grandchild.foo(), 'bar');
assert.equal(p.children[0].grandchild.foo(), 'bar');
- done();
});
it('hooks/middleware for custom methods (gh-6385) (gh-7456)', function() {
@@ -4758,7 +4693,7 @@ describe('document', function() {
});
});
- it('toString() as custom method (gh-6538)', function(done) {
+ it('toString() as custom method (gh-6538)', function() {
const commentSchema = new Schema({ title: String });
commentSchema.methods.toString = function() {
return `${this.constructor.modelName}(${this.title})`;
@@ -4766,10 +4701,9 @@ describe('document', function() {
const Comment = db.model('Comment', commentSchema);
const c = new Comment({ title: 'test' });
assert.strictEqual('Comment(test)', `${c}`);
- done();
});
- it('setting to discriminator (gh-4935)', function(done) {
+ it('setting to discriminator (gh-4935)', function() {
const Buyer = db.model('Test1', new Schema({
name: String,
vehicle: { type: Schema.Types.ObjectId, ref: 'Test' }
@@ -4786,8 +4720,6 @@ describe('document', function() {
assert.ok(nick.vehicle === eleanor);
assert.ok(nick.vehicle instanceof Car);
assert.equal(nick.vehicle.name, 'Eleanor');
-
- done();
});
it('handles errors in sync validators (gh-2185)', function(done) {
@@ -4899,7 +4831,7 @@ describe('document', function() {
then(doc => assert.equal(doc.sub.val, 'test'));
});
- it('nested docs toObject() clones (gh-5008)', function(done) {
+ it('nested docs toObject() clones (gh-5008)', function() {
const schema = new mongoose.Schema({
sub: {
height: Number
@@ -4922,11 +4854,9 @@ describe('document', function() {
doc.sub.height = 55;
assert.equal(doc.sub.height, 55);
assert.equal(leanDoc.height, 3);
-
- done();
});
- it('toObject() with null (gh-5143)', function(done) {
+ it('toObject() with null (gh-5143)', function() {
const schema = new mongoose.Schema({
customer: {
name: { type: String, required: false }
@@ -4939,11 +4869,9 @@ describe('document', function() {
model.customer = null;
assert.strictEqual(model.toObject().customer, null);
assert.strictEqual(model.toObject({ getters: true }).customer, null);
-
- done();
});
- it('handles array subdocs with single nested subdoc default (gh-5162)', function(done) {
+ it('handles array subdocs with single nested subdoc default (gh-5162)', function() {
const RatingsItemSchema = new mongoose.Schema({
value: Number
}, { versionKey: false, _id: false });
@@ -4967,10 +4895,9 @@ describe('document', function() {
// Should not throw
const r = new Restaurant();
assert.deepEqual(r.toObject().menu, []);
- done();
});
- it('iterating through nested doc keys (gh-5078)', function(done) {
+ it('iterating through nested doc keys (gh-5078)', function() {
const schema = new Schema({
nested: {
test1: String,
@@ -4988,14 +4915,13 @@ describe('document', function() {
assert.deepEqual(doc.toObject({ virtuals: true }).tests, ['a', 'b']);
- // Should not throw
- require('util').inspect(doc);
+ assert.doesNotThrow(function() {
+ require('util').inspect(doc);
+ });
JSON.stringify(doc);
-
- done();
});
- it('deeply nested virtual paths (gh-5250)', function(done) {
+ it('deeply nested virtual paths (gh-5250)', function() {
const TestSchema = new Schema({});
TestSchema.
virtual('a.b.c').
@@ -5009,8 +4935,6 @@ describe('document', function() {
const TestModel = db.model('Test', TestSchema);
const t = new TestModel({ 'a.b.c': 5 });
assert.equal(t.a.b.c, 5);
-
- done();
});
it('nested virtual when populating with parent projected out (gh-7491)', function() {
@@ -5241,7 +5165,7 @@ describe('document', function() {
});
});
- it('setting populated path with typeKey (gh-5313)', function(done) {
+ it('setting populated path with typeKey (gh-5313)', function() {
const personSchema = Schema({
name: { $type: String },
favorite: { $type: Schema.Types.ObjectId, ref: 'Book' },
@@ -5266,8 +5190,6 @@ describe('document', function() {
assert.equal(person.books[0].title, 'The Jungle Book');
assert.equal(person.books[1].title, '1984');
-
- done();
});
it('save twice with write concern (gh-5294)', function(done) {
@@ -5311,7 +5233,7 @@ describe('document', function() {
});
});
- it('dotted virtuals in toObject (gh-5473)', function(done) {
+ it('dotted virtuals in toObject (gh-5473)', function() {
const schema = new mongoose.Schema({}, {
toObject: { virtuals: true },
toJSON: { virtuals: true }
@@ -5335,7 +5257,6 @@ describe('document', function() {
b: 2
});
assert.equal(m.toObject({ virtuals: false }).test, void 0);
- done();
});
it('dotted virtuals in toObject (gh-5506)', function(done) {
@@ -5372,7 +5293,7 @@ describe('document', function() {
catch(done);
});
- it('parent props not in child (gh-5470)', function(done) {
+ it('parent props not in child (gh-5470)', function() {
const employeeSchema = new mongoose.Schema({
name: {
first: String,
@@ -5394,7 +5315,6 @@ describe('document', function() {
assert.ok(ownPropertyNames.indexOf('department') === -1, ownPropertyNames.join(','));
assert.ok(ownPropertyNames.indexOf('first') !== -1, ownPropertyNames.join(','));
assert.ok(ownPropertyNames.indexOf('last') !== -1, ownPropertyNames.join(','));
- done();
});
it('modifying array with existing ids (gh-5523)', function(done) {
@@ -5601,7 +5521,7 @@ describe('document', function() {
});
});
- it('push populated doc onto empty array triggers manual population (gh-5504)', function(done) {
+ it('push populated doc onto empty array triggers manual population (gh-5504)', function() {
const ReferringSchema = new Schema({
reference: [{
type: Schema.Types.ObjectId,
@@ -5635,8 +5555,6 @@ describe('document', function() {
referrerE.reference.addToSet(referenceB);
assert.ok(referrerE.reference[0] instanceof Referrer);
-
- done();
});
it('single nested conditional required scope (gh-5569)', function(done) {
@@ -5677,7 +5595,7 @@ describe('document', function() {
});
});
- it('single nested setters only get called once (gh-5601)', function(done) {
+ it('single nested setters only get called once (gh-5601)', function() {
const vals = [];
const ChildSchema = new mongoose.Schema({
number: {
@@ -5703,7 +5621,6 @@ describe('document', function() {
p.child = { number: '555.555.0123' };
assert.equal(vals.length, 1);
assert.equal(vals[0], '555.555.0123');
- done();
});
it('single getters only get called once (gh-7442)', function() {
@@ -5781,7 +5698,7 @@ describe('document', function() {
});
});
- it('handles array defaults correctly (gh-5780)', function(done) {
+ it('handles array defaults correctly (gh-5780)', function() {
const testSchema = new Schema({
nestedArr: {
type: [[Number]],
@@ -5797,8 +5714,6 @@ describe('document', function() {
t.nestedArr.push([1, 2]);
const t2 = new Test({});
assert.deepEqual(t2.toObject().nestedArr, [[0, 1]]);
-
- done();
});
it('sets path to the empty string on save after query (gh-6477)', function() {
@@ -5863,7 +5778,7 @@ describe('document', function() {
});
});
- it('virtuals with no getters return undefined (gh-6223)', function(done) {
+ it('virtuals with no getters return undefined (gh-6223)', function() {
const personSchema = new mongoose.Schema({
name: { type: String },
children: [{
@@ -5892,11 +5807,9 @@ describe('document', function() {
assert.strictEqual(person.favoriteChild, void 0);
assert.ok(!('favoriteChild' in person.toJSON()));
assert.ok(!('favoriteChild' in person.toObject()));
-
- done();
});
- it('add default getter/setter (gh-6262)', function(done) {
+ it('add default getter/setter (gh-6262)', function() {
const testSchema = new mongoose.Schema({});
testSchema.virtual('totalValue');
@@ -5909,8 +5822,6 @@ describe('document', function() {
const doc = new Test();
doc.totalValue = 5;
assert.equal(doc.totalValue, 5);
-
- done();
});
it('nested virtuals + nested toJSON (gh-6294)', function() {
@@ -5938,7 +5849,7 @@ describe('document', function() {
});
});
- it('Disallows writing to __proto__ and other special properties', function(done) {
+ it('Disallows writing to __proto__ and other special properties', function() {
const schema = new mongoose.Schema({
name: String
}, { strict: false });
@@ -5954,8 +5865,6 @@ describe('document', function() {
doc.set('constructor.prototype.z', 'baz');
assert.strictEqual(Model.z, void 0);
-
- done();
});
it('save() depopulates pushed arrays (gh-6048)', function() {
@@ -6334,7 +6243,7 @@ describe('document', function() {
});
});
- it('accessing arrays in setters on initial document creation (gh-6155)', function(done) {
+ it('accessing arrays in setters on initial document creation (gh-6155)', function() {
const artistSchema = new mongoose.Schema({
name: {
type: String,
@@ -6353,11 +6262,9 @@ describe('document', function() {
const artist = new Artist({ name: 'Motley Crue' });
assert.deepEqual(artist.toObject().keywords, ['Motley', 'Crue']);
-
- done();
});
- it('handles 2nd level nested field with null child (gh-6187)', function(done) {
+ it('handles 2nd level nested field with null child (gh-6187)', function() {
const NestedSchema = new Schema({
parent: new Schema({
name: String,
@@ -6375,8 +6282,6 @@ describe('document', function() {
});
assert.equal(n.parent.name, 'foo');
-
- done();
});
it('does not call default function on init if value set (gh-6410)', function() {
@@ -6600,7 +6505,7 @@ describe('document', function() {
delete Array.prototype.remove;
});
- it('handles clobbered Array.prototype.remove (gh-6431)', function(done) {
+ it('handles clobbered Array.prototype.remove (gh-6431)', function() {
Object.defineProperty(Array.prototype, 'remove', {
value: 42,
configurable: true,
@@ -6612,7 +6517,6 @@ describe('document', function() {
const doc = new MyModel();
assert.deepEqual(doc.toObject().arr, []);
- done();
});
it('calls array validators again after save (gh-6818)', function() {
@@ -7389,7 +7293,9 @@ describe('document', function() {
assert.equal(cu.profile.name, 'foo');
assert.equal(cu.profile.email, 'bar');
- cu.toObject(); // shouldn't throw
+ assert.doesNotThrow(function() {
+ cu.toObject();
+ });
});
it('setting single nested subdoc with custom date types and getters/setters (gh-7601)', function() {
@@ -9094,4 +9000,25 @@ describe('document', function() {
});
});
});
+
+ it('supports skipping defaults on a document (gh-8271)', function() {
+ const testSchema = new mongoose.Schema({
+ testTopLevel: { type: String, default: 'foo' },
+ testNested: {
+ prop: { type: String, default: 'bar' }
+ },
+ testArray: [{ prop: { type: String, defualt: 'baz' } }],
+ testSingleNested: new Schema({
+ prop: { type: String, default: 'qux' }
+ })
+ });
+ const Test = db.model('Test', testSchema);
+
+ const doc = new Test({ testArray: [{}], testSingleNested: {} }, null,
+ { defaults: false });
+ assert.ok(!doc.testTopLevel);
+ assert.ok(!doc.testNested.prop);
+ assert.ok(!doc.testArray[0].prop);
+ assert.ok(!doc.testSingleNested.prop);
+ });
});
diff --git a/test/document.unit.test.js b/test/document.unit.test.js
index 874c93674d0..bc7fbd3bc50 100644
--- a/test/document.unit.test.js
+++ b/test/document.unit.test.js
@@ -12,7 +12,7 @@ const storeShard = require('../lib/plugins/sharding').storeShard;
const mongoose = start.mongoose;
describe('sharding', function() {
- it('should handle shard keys properly (gh-2127)', function(done) {
+ it('should handle shard keys properly (gh-2127)', function() {
const mockSchema = {
options: {
shardKey: { date: 1 }
@@ -29,7 +29,6 @@ describe('sharding', function() {
storeShard.call(d);
assert.equal(d.$__.shardval.date, currentTime);
- done();
});
});
@@ -49,24 +48,21 @@ describe('toObject()', function() {
Stub.prototype = Object.create(mongoose.Document.prototype);
});
- it('should inherit options from schema', function(done) {
+ it('should inherit options from schema', function() {
const d = new Stub();
assert.deepEqual(d.toObject(), { empty: {}, virtual: 'test' });
- done();
});
- it('can overwrite schema-set default options', function(done) {
+ it('can overwrite schema-set default options', function() {
const d = new Stub();
assert.deepEqual(d.toObject({ minimize: true, virtuals: false }), {});
- done();
});
- it('doesnt crash with empty object (gh-3130)', function(done) {
+ it('doesnt crash with empty object (gh-3130)', function() {
const d = new Stub();
d._doc = undefined;
assert.doesNotThrow(function() {
d.toObject();
});
- done();
});
});
diff --git a/test/helpers/update.castArrayFilters.test.js b/test/helpers/update.castArrayFilters.test.js
index 17e64de2991..fac39ca9fc1 100644
--- a/test/helpers/update.castArrayFilters.test.js
+++ b/test/helpers/update.castArrayFilters.test.js
@@ -42,6 +42,31 @@ describe('castArrayFilters', function() {
done();
});
+ it('casts on multiple fields', function(done) {
+ const schema = new Schema({
+ comments: [{
+ text: String,
+ replies: [{
+ beginAt: Date,
+ endAt: Date
+ }]
+ }]
+ });
+ const q = new Query();
+ q.schema = schema;
+
+ q.updateOne({}, { $set: { 'comments.$[x].replies.$[y].endAt': '2019-01-01' } }, {
+ arrayFilters: [{ 'x.text': 123 }, { 'y.beginAt': { $gte: '2018-01-01' }, 'y.endAt': { $lt: '2020-01-01' } }]
+ });
+ castArrayFilters(q);
+
+ assert.strictEqual(q.options.arrayFilters[0]['x.text'], '123');
+ assert.ok(q.options.arrayFilters[1]['y.beginAt'].$gte instanceof Date);
+ assert.ok(q.options.arrayFilters[1]['y.endAt'].$lt instanceof Date);
+
+ done();
+ });
+
it('sane error on same filter twice', function(done) {
const schema = new Schema({
comments: [{
diff --git a/test/index.test.js b/test/index.test.js
index a8025689c86..993d69f78b5 100644
--- a/test/index.test.js
+++ b/test/index.test.js
@@ -44,26 +44,24 @@ describe('mongoose module:', function() {
});
});
- it('legacy pluralize by default (gh-5958)', function(done) {
+ it('legacy pluralize by default (gh-5958)', function() {
const mongoose = new Mongoose();
mongoose.model('User', new Schema({}));
assert.equal(mongoose.model('User').collection.name, 'users');
- done();
});
- it('returns legacy pluralize function by default', function(done) {
+ it('returns legacy pluralize function by default', function() {
const legacyPluralize = require('mongoose-legacy-pluralize');
const mongoose = new Mongoose();
const pluralize = mongoose.pluralize();
assert.equal(pluralize, legacyPluralize);
- done();
});
- it('sets custom pluralize function (gh-5877)', function(done) {
+ it('sets custom pluralize function (gh-5877)', function() {
const mongoose = new Mongoose();
// some custom function of type (str: string) => string
@@ -75,7 +73,6 @@ describe('mongoose module:', function() {
mongoose.model('User', new Schema({}));
assert.equal(mongoose.model('User').collection.name, 'User');
- done();
});
it('debug to stream (gh-7018)', function() {
@@ -100,7 +97,7 @@ describe('mongoose module:', function() {
});
});
- it('{g,s}etting options', function(done) {
+ it('{g,s}etting options', function() {
const mongoose = new Mongoose();
mongoose.set('runValidators', 'b');
@@ -109,17 +106,14 @@ describe('mongoose module:', function() {
assert.equal(mongoose.get('runValidators'), 'b');
assert.equal(mongoose.set('runValidators'), 'b');
assert.equal(mongoose.get('useNewUrlParser'), 'c');
- done();
});
- it('allows `const { model } = mongoose` (gh-3768)', function(done) {
+ it('allows `const { model } = mongoose` (gh-3768)', function() {
const model = mongoose.model;
model('gh3768', new Schema({ name: String }));
assert.ok(mongoose.models['gh3768']);
-
- done();
});
it('options object (gh-8144)', function() {
@@ -128,7 +122,7 @@ describe('mongoose module:', function() {
assert.strictEqual(mongoose.options.bufferCommands, false);
});
- it('bufferCommands option (gh-5879)', function(done) {
+ it('bufferCommands option (gh-5879)', function() {
const mongoose = new Mongoose();
mongoose.set('bufferCommands', false);
@@ -136,11 +130,9 @@ describe('mongoose module:', function() {
const M = mongoose.model('Test', new Schema({}));
assert.ok(!M.collection.buffer);
-
- done();
});
- it('cloneSchemas option (gh-6274)', function(done) {
+ it('cloneSchemas option (gh-6274)', function() {
const mongoose = new Mongoose();
mongoose.set('cloneSchemas', true);
@@ -148,17 +140,17 @@ describe('mongoose module:', function() {
const s = new Schema({});
const M = mongoose.model('Test', s);
assert.ok(M.schema !== s);
- mongoose.model('Test', M.schema); // Shouldn't throw
+ assert.doesNotThrow(function() {
+ mongoose.model('Test', M.schema);
+ });
mongoose.set('cloneSchemas', false);
const M2 = mongoose.model('Test2', s);
assert.ok(M2.schema === s);
-
- done();
});
- it('objectIdGetter option (gh-6588)', function(done) {
+ it('objectIdGetter option (gh-6588)', function() {
const mongoose = new Mongoose();
let o = new mongoose.Types.ObjectId();
@@ -173,8 +165,6 @@ describe('mongoose module:', function() {
o = new mongoose.Types.ObjectId();
assert.strictEqual(o._id, o);
-
- done();
});
it('runValidators option (gh-6865) (gh-6578)', function() {
@@ -219,7 +209,7 @@ describe('mongoose module:', function() {
});
});
- it('toJSON options (gh-6815)', function(done) {
+ it('toJSON options (gh-6815)', function() {
const mongoose = new Mongoose();
mongoose.set('toJSON', { virtuals: true });
@@ -241,11 +231,9 @@ describe('mongoose module:', function() {
doc = new M2();
assert.equal(doc.toJSON({ virtuals: false }).foo, void 0);
assert.equal(doc.toJSON().foo, 'bar');
-
- done();
});
- it('toObject options (gh-6815)', function(done) {
+ it('toObject options (gh-6815)', function() {
const mongoose = new Mongoose();
mongoose.set('toObject', { virtuals: true });
@@ -257,10 +245,9 @@ describe('mongoose module:', function() {
const doc = new M();
assert.equal(doc.toObject().foo, 42);
assert.strictEqual(doc.toJSON().foo, void 0);
- done();
});
- it('strict option (gh-6858)', function(done) {
+ it('strict option (gh-6858)', function() {
const mongoose = new Mongoose();
// With strict: throw, no schema-level override
@@ -299,8 +286,6 @@ describe('mongoose module:', function() {
assert.strictEqual(doc.$__.strictMode, false);
assert.equal(doc.toObject().bar, 'baz');
-
- done();
});
it('declaring global plugins (gh-5690)', function(done) {
@@ -435,7 +420,7 @@ describe('mongoose module:', function() {
return Promise.resolve();
});
- it('top-level ObjectId, Decimal128, Mixed (gh-6760)', function(done) {
+ it('top-level ObjectId, Decimal128, Mixed (gh-6760)', function() {
const mongoose = new Mongoose();
const schema = new Schema({
@@ -450,8 +435,6 @@ describe('mongoose module:', function() {
assert.ok(doc.testId instanceof mongoose.Types.ObjectId);
assert.ok(doc.testNum instanceof mongoose.Types.Decimal128);
-
- done();
});
it('stubbing now() for timestamps (gh-6728)', function() {
@@ -580,7 +563,7 @@ describe('mongoose module:', function() {
});
describe('model()', function() {
- it('accessing a model that hasn\'t been defined', function(done) {
+ it('accessing a model that hasn\'t been defined', function() {
const mong = new Mongoose();
let thrown = false;
@@ -592,10 +575,9 @@ describe('mongoose module:', function() {
}
assert.equal(thrown, true);
- done();
});
- it('returns the model at creation', function(done) {
+ it('returns the model at creation', function() {
const Named = mongoose.model('Named', new Schema({ name: String }));
const n1 = new Named();
assert.equal(n1.name, null);
@@ -606,7 +588,6 @@ describe('mongoose module:', function() {
const Numbered = mongoose.model('Numbered', schema, collection);
const n3 = new Numbered({ number: 1234 });
assert.equal(n3.number.valueOf(), 1234);
- done();
});
it('prevents overwriting pre-existing models', function(done) {
@@ -634,7 +615,7 @@ describe('mongoose module:', function() {
done();
});
- it('allows passing identical name+schema+collection args (gh-5767)', function(done) {
+ it('allows passing identical name+schema+collection args (gh-5767)', function() {
const m = new Mongoose;
const schema = new Schema;
const model = m.model('A', schema, 'AA');
@@ -644,16 +625,12 @@ describe('mongoose module:', function() {
});
assert.equal(model, m.model('A', model.schema, 'AA'));
-
- done();
});
- it('throws on unknown model name', function(done) {
+ it('throws on unknown model name', function() {
assert.throws(function() {
mongoose.model('iDoNotExist!');
}, /Schema hasn't been registered/);
-
- done();
});
describe('passing collection name', function() {
@@ -752,14 +729,12 @@ describe('mongoose module:', function() {
assert.equal(typeof mongoose.Error.VersionError, 'function');
}
- it('of module', function(done) {
+ it('of module', function() {
test(mongoose);
- done();
});
- it('of new Mongoose instances', function(done) {
+ it('of new Mongoose instances', function() {
test(new mongoose.Mongoose);
- done();
});
it('of result from .connect() (gh-3940)', function(done) {
@@ -778,5 +753,57 @@ describe('mongoose module:', function() {
done();
});
});
+
+ it('can set `setDefaultsOnInsert` as a global option (gh-9032)', function() {
+ return co(function* () {
+ const m = new mongoose.Mongoose();
+ m.set('setDefaultsOnInsert', true);
+ const db = yield m.connect('mongodb://localhost:27017/mongoose_test_9032');
+
+ const schema = new m.Schema({
+ title: String,
+ genre: { type: String, default: 'Action' }
+ }, { collection: 'movies_1' });
+
+ const Movie = db.model('Movie', schema);
+
+
+ yield Movie.updateOne(
+ {},
+ { title: 'Cloud Atlas' },
+ { upsert: true }
+ );
+
+ // lean is necessary to avoid defaults by casting
+ const movie = yield Movie.findOne({ title: 'Cloud Atlas' }).lean();
+ assert.equal(movie.genre, 'Action');
+ });
+ });
+
+ it('setting `setDefaultOnInsert` on operation has priority over base option (gh-9032)', function() {
+ return co(function* () {
+ const m = new mongoose.Mongoose();
+ m.set('setDefaultsOnInsert', true);
+ const db = yield m.connect('mongodb://localhost:27017/mongoose_test_9032');
+
+ const schema = new m.Schema({
+ title: String,
+ genre: { type: String, default: 'Action' }
+ }, { collection: 'movies_2' });
+
+ const Movie = db.model('Movie', schema);
+
+
+ yield Movie.updateOne(
+ {},
+ { title: 'The Man From Earth' },
+ { upsert: true, setDefaultsOnInsert: false }
+ );
+
+ // lean is necessary to avoid defaults by casting
+ const movie = yield Movie.findOne({ title: 'The Man From Earth' }).lean();
+ assert.ok(!movie.genre);
+ });
+ });
});
-});
+});
\ No newline at end of file
diff --git a/test/model.aggregate.test.js b/test/model.aggregate.test.js
index 98dc7b1b239..d9c8a576db0 100644
--- a/test/model.aggregate.test.js
+++ b/test/model.aggregate.test.js
@@ -114,14 +114,12 @@ describe('model aggregate', function() {
});
});
- it('when returning Aggregate', function(done) {
+ it('when returning Aggregate', function() {
assert(A.aggregate([project]) instanceof Aggregate);
- done();
});
- it('throws when passing object (gh-6732)', function(done) {
+ it('throws when passing object (gh-6732)', function() {
assert.throws(() => A.aggregate({}), /disallows passing a spread/);
- done();
});
it('can use helper for $out', function(done) {
diff --git a/test/model.discriminator.test.js b/test/model.discriminator.test.js
index d805a35b301..d6fe821c625 100644
--- a/test/model.discriminator.test.js
+++ b/test/model.discriminator.test.js
@@ -87,23 +87,21 @@ describe('model', function() {
Employee = Person.discriminator('Employee', EmployeeSchema);
});
- it('model defaults without discriminator', function(done) {
+ it('model defaults without discriminator', function() {
const Model = db.model('Test1', new Schema());
assert.equal(Model.discriminators, undefined);
- done();
});
- it('is instance of root', function(done) {
+ it('is instance of root', function() {
assert.equal(Employee.baseModelName, 'Test');
const employee = new Employee();
assert.ok(employee instanceof Person);
assert.ok(employee instanceof Employee);
assert.strictEqual(employee.__proto__.constructor, Employee);
assert.strictEqual(employee.__proto__.__proto__.constructor, Person);
- done();
});
- it('can define static and instance methods', function(done) {
+ it('can define static and instance methods', function() {
function BossBaseSchema() {
Schema.apply(this, arguments);
@@ -131,7 +129,6 @@ describe('model', function() {
assert.equal(boss.notInstanceMethod, undefined);
assert.equal(Boss.currentPresident(), 'obama');
assert.equal(Boss.notStaticMethod, undefined);
- done();
});
it('sets schema root discriminator mapping', function(done) {
@@ -144,54 +141,49 @@ describe('model', function() {
done();
});
- it('adds discriminatorKey to schema with default as name', function(done) {
+ it('adds discriminatorKey to schema with default as name', function() {
const type = Employee.schema.paths.__t;
assert.equal(type.options.type, String);
assert.equal(type.options.default, 'Employee');
- done();
});
- it('adds discriminator to Model.discriminators object', function(done) {
+ it('adds discriminator to Model.discriminators object', function() {
assert.equal(Object.keys(Person.discriminators).length, 1);
assert.equal(Person.discriminators['Employee'], Employee);
const newName = 'model-discriminator-' + random();
const NewDiscriminatorType = Person.discriminator(newName, new Schema());
assert.equal(Object.keys(Person.discriminators).length, 2);
assert.equal(Person.discriminators[newName], NewDiscriminatorType);
- done();
});
- it('throws error on invalid schema', function(done) {
+ it('throws error on invalid schema', function() {
assert.throws(
function() {
Person.discriminator('Foo');
},
/You must pass a valid discriminator Schema/
);
- done();
});
- it('throws error when attempting to nest discriminators', function(done) {
+ it('throws error when attempting to nest discriminators', function() {
assert.throws(
function() {
Employee.discriminator('model-discriminator-foo', new Schema());
},
/Discriminator "model-discriminator-foo" can only be a discriminator of the root model/
);
- done();
});
- it('throws error when discriminator has mapped discriminator key in schema', function(done) {
+ it('throws error when discriminator has mapped discriminator key in schema', function() {
assert.throws(
function() {
Person.discriminator('model-discriminator-foo', new Schema({ __t: String }));
},
/Discriminator "model-discriminator-foo" cannot have field with name "__t"/
);
- done();
});
- it('throws error when discriminator has mapped discriminator key in schema with discriminatorKey option set', function(done) {
+ it('throws error when discriminator has mapped discriminator key in schema with discriminatorKey option set', function() {
assert.throws(
function() {
const Foo = db.model('Test1', new Schema({}, { discriminatorKey: '_type' }));
@@ -199,10 +191,9 @@ describe('model', function() {
},
/Discriminator "Bar" cannot have field with name "_type"/
);
- done();
});
- it('throws error when discriminator with taken name is added', function(done) {
+ it('throws error when discriminator with taken name is added', function() {
const Foo = db.model('Test1', new Schema({}));
Foo.discriminator('Token', new Schema());
assert.throws(
@@ -211,10 +202,9 @@ describe('model', function() {
},
/Discriminator with name "Token" already exists/
);
- done();
});
- it('throws error if model name is taken (gh-4148)', function(done) {
+ it('throws error if model name is taken (gh-4148)', function() {
const Foo = db.model('Test1', new Schema({}));
db.model('Test', new Schema({}));
assert.throws(
@@ -222,7 +212,6 @@ describe('model', function() {
Foo.discriminator('Test', new Schema());
},
/Cannot overwrite `Test`/);
- done();
});
it('works with nested schemas (gh-2821)', function(done) {
@@ -270,32 +259,28 @@ describe('model', function() {
done();
});
- it('is not customizable', function(done) {
+ it('is not customizable', function() {
const CustomizedSchema = new Schema({}, { capped: true });
assert.throws(function() {
Person.discriminator('model-discriminator-custom', CustomizedSchema);
}, /Can't customize discriminator option capped/);
-
- done();
});
});
describe('root schema inheritance', function() {
- it('inherits field mappings', function(done) {
+ it('inherits field mappings', function() {
assert.strictEqual(Employee.schema.path('name'), Person.schema.path('name'));
assert.strictEqual(Employee.schema.path('gender'), Person.schema.path('gender'));
assert.equal(Person.schema.paths.department, undefined);
- done();
});
- it('inherits validators', function(done) {
+ it('inherits validators', function() {
assert.strictEqual(Employee.schema.path('gender').validators, PersonSchema.path('gender').validators);
assert.strictEqual(Employee.schema.path('department').validators, EmployeeSchema.path('department').validators);
- done();
});
- it('does not inherit and override fields that exist', function(done) {
+ it('does not inherit and override fields that exist', function() {
const FemaleSchema = new Schema({ gender: { type: String, default: 'F' } }),
Female = Person.discriminator('model-discriminator-female', FemaleSchema);
@@ -304,38 +289,33 @@ describe('model', function() {
assert.notStrictEqual(gender, Person.schema.paths.gender);
assert.equal(gender.instance, 'String');
assert.equal(gender.options.default, 'F');
- done();
});
- it('inherits methods', function(done) {
+ it('inherits methods', function() {
const employee = new Employee();
assert.strictEqual(employee.getFullName, PersonSchema.methods.getFullName);
assert.strictEqual(employee.getDepartment, EmployeeSchema.methods.getDepartment);
assert.equal((new Person).getDepartment, undefined);
- done();
});
- it('inherits statics', function(done) {
+ it('inherits statics', function() {
assert.strictEqual(Employee.findByGender, PersonSchema.statics.findByGender);
assert.strictEqual(Employee.findByDepartment, EmployeeSchema.statics.findByDepartment);
assert.equal(Person.findByDepartment, undefined);
- done();
});
- it('inherits virtual (g.s)etters', function(done) {
+ it('inherits virtual (g.s)etters', function() {
const employee = new Employee();
employee.name.full = 'John Doe';
assert.equal(employee.name.full, 'John Doe');
- done();
});
- it('does not inherit indexes', function(done) {
+ it('does not inherit indexes', function() {
assert.deepEqual(Person.schema.indexes(), [[{ name: 1 }, { background: true }]]);
assert.deepEqual(Employee.schema.indexes(), [[{ department: 1 }, { background: true }]]);
- done();
});
- it('gets options overridden by root options except toJSON and toObject', function(done) {
+ it('gets options overridden by root options except toJSON and toObject', function() {
const personOptions = clone(Person.schema.options),
employeeOptions = clone(Employee.schema.options);
@@ -345,7 +325,6 @@ describe('model', function() {
delete employeeOptions.toObject;
assert.deepEqual(personOptions, employeeOptions);
- done();
});
it('does not allow setting discriminator key (gh-2041)', function(done) {
@@ -390,14 +369,13 @@ describe('model', function() {
});
});
- it('with typeKey (gh-4339)', function(done) {
+ it('with typeKey (gh-4339)', function() {
const options = { typeKey: '$type', discriminatorKey: '_t' };
const schema = new Schema({ test: { $type: String } }, options);
const Model = db.model('Test', schema);
Model.discriminator('D', new Schema({
test2: String
}, { typeKey: '$type' }));
- done();
});
describe('applyPluginsToDiscriminators', function() {
@@ -408,7 +386,7 @@ describe('model', function() {
m.set('applyPluginsToDiscriminators', true);
});
- it('works (gh-4965)', function(done) {
+ it('works (gh-4965)', function() {
const schema = new m.Schema({ test: String });
let called = 0;
m.plugin(function() {
@@ -420,8 +398,6 @@ describe('model', function() {
});
Model.discriminator('D', childSchema);
assert.equal(called, 2);
-
- done();
});
it('works with customized options (gh-7458)', function() {
@@ -444,8 +420,6 @@ describe('model', function() {
// Should not throw
Model.discriminator('TestSub', subSchema);
-
- return Promise.resolve();
});
});
@@ -603,7 +577,7 @@ describe('model', function() {
});
});
- it('clone() allows reusing schemas (gh-5098)', function(done) {
+ it('clone() allows reusing schemas (gh-5098)', function() {
const personSchema = new Schema({
name: String
}, { discriminatorKey: 'kind' });
@@ -616,7 +590,6 @@ describe('model', function() {
Person.discriminator('Parent', parentSchema.clone());
// Should not throw
Person.discriminator('Parent2', parentSchema.clone());
- done();
});
it('clone() allows reusing with different models (gh-5721)', function(done) {
@@ -698,7 +671,7 @@ describe('model', function() {
});
});
- it('reusing schema for discriminators (gh-5684)', function(done) {
+ it('reusing schema for discriminators (gh-5684)', function() {
const ParentSchema = new Schema({});
const ChildSchema = new Schema({ name: String });
@@ -724,11 +697,9 @@ describe('model', function() {
assert.equal(doc1.stuff[0].name, 'test');
assert.equal(doc2.things.length, 1);
assert.equal(doc2.things[0].name, 'test');
-
- done();
});
- it('overwrites nested paths in parent schema (gh-6076)', function(done) {
+ it('overwrites nested paths in parent schema (gh-6076)', function() {
const schema = mongoose.Schema({
account: {
type: Object
@@ -756,10 +727,7 @@ describe('model', function() {
info: 'AAAAAAAAAAAAAAAAAAAAAAAA'
});
- // Should not throw
assert.ifError(d1.validateSync());
-
- done();
});
it('nested discriminator key with projecting in parent (gh-5775)', function(done) {
@@ -1074,7 +1042,7 @@ describe('model', function() {
catch(done);
});
- it('embedded with single nested subdocs and tied value (gh-8164)', function() {
+ it('embedded with single nested subdocs and tied value (gh-8164) (gh-9108)', function() {
const eventSchema = new Schema({ message: String },
{ discriminatorKey: 'kind', _id: false });
@@ -1087,13 +1055,13 @@ describe('model', function() {
}, { _id: false }), 'purchase');
const MyModel = db.model('Test1', trackSchema);
- const doc1 = {
+ let doc1 = {
event: {
kind: 'click',
element: 'Amazon Link'
}
};
- const doc2 = {
+ let doc2 = {
event: {
kind: 'purchase',
product: 'Professional AngularJS'
@@ -1101,8 +1069,8 @@ describe('model', function() {
};
return MyModel.create([doc1, doc2]).
then(function(docs) {
- const doc1 = docs[0];
- const doc2 = docs[1];
+ doc1 = docs[0];
+ doc2 = docs[1];
assert.equal(doc1.event.kind, 'click');
assert.equal(doc1.event.element, 'Amazon Link');
@@ -1111,6 +1079,14 @@ describe('model', function() {
assert.equal(doc2.event.kind, 'purchase');
assert.equal(doc2.event.product, 'Professional AngularJS');
assert.ok(!doc2.event.element);
+
+ return MyModel.updateOne({ 'event.kind': 'click' }, {
+ 'event.element': 'Pluralsight Link'
+ });
+ }).
+ then(() => MyModel.findById(doc1._id)).
+ then(doc => {
+ assert.equal(doc.event.element, 'Pluralsight Link');
});
});
diff --git a/test/model.indexes.test.js b/test/model.indexes.test.js
index 41ff749f85e..89b3db7d981 100644
--- a/test/model.indexes.test.js
+++ b/test/model.indexes.test.js
@@ -452,6 +452,24 @@ describe('model', function() {
});
});
+ it('sets correct partialFilterExpression for document array (gh-9091)', function() {
+ const childSchema = new Schema({ name: String });
+ childSchema.index({ name: 1 }, { partialFilterExpression: { name: { $exists: true } } });
+ const schema = new Schema({ arr: [childSchema] });
+ const Model = db.model('Test', schema);
+
+ return Model.init().
+ then(() => Model.syncIndexes()).
+ then(() => Model.listIndexes()).
+ then(indexes => {
+ assert.equal(indexes.length, 2);
+ assert.ok(indexes[1].partialFilterExpression);
+ assert.deepEqual(indexes[1].partialFilterExpression, {
+ 'arr.name': { $exists: true }
+ });
+ });
+ });
+
describe('discriminators with unique', function() {
this.timeout(5000);
diff --git a/test/model.update.test.js b/test/model.update.test.js
index 1b6b677cfea..8e8c517d865 100644
--- a/test/model.update.test.js
+++ b/test/model.update.test.js
@@ -3382,6 +3382,31 @@ describe('model: updateOne: ', function() {
then(doc => assert.strictEqual(doc.nested.notInSchema, void 0));
});
+ it('handles timestamp properties in nested paths when overwriting parent path (gh-9105)', function() {
+ const SampleSchema = Schema({ nested: { test: String } }, {
+ timestamps: {
+ createdAt: 'nested.createdAt',
+ updatedAt: 'nested.updatedAt'
+ }
+ });
+ const Test = db.model('Test', SampleSchema);
+
+ return co(function*() {
+ const doc = yield Test.create({ nested: { test: 'foo' } });
+ assert.ok(doc.nested.updatedAt);
+ assert.ok(doc.nested.createdAt);
+
+ yield cb => setTimeout(cb, 10);
+ yield Test.updateOne({ _id: doc._id }, { nested: { test: 'bar' } });
+
+ const fromDb = yield Test.findOne({ _id: doc._id });
+ assert.ok(fromDb.nested.updatedAt);
+ assert.ok(fromDb.nested.updatedAt > doc.nested.updatedAt);
+ assert.ok(fromDb.nested.createdAt);
+ assert.ok(fromDb.nested.createdAt > doc.nested.createdAt);
+ });
+ });
+
describe('mongodb 42 features', function() {
before(function(done) {
start.mongodVersion((err, version) => {
diff --git a/test/schema.documentarray.test.js b/test/schema.documentarray.test.js
index 983bf9634de..89937a538d1 100644
--- a/test/schema.documentarray.test.js
+++ b/test/schema.documentarray.test.js
@@ -98,4 +98,20 @@ describe('schema.documentarray', function() {
assert.equal(doc.nested[0].length, 3);
assert.equal(doc.nested[0][1].title, 'second');
});
+
+ it('supports `set()` (gh-8883)', function() {
+ mongoose.deleteModel(/Test/);
+ mongoose.Schema.Types.DocumentArray.set('_id', false);
+
+ const Model = mongoose.model('Test', mongoose.Schema({
+ arr: { type: [{ name: String }] }
+ }));
+
+ const doc = new Model({ arr: [{ name: 'test' }] });
+
+ assert.equal(doc.arr.length, 1);
+ assert.ok(!doc.arr[0]._id);
+
+ mongoose.Schema.Types.DocumentArray.defaultOptions = {};
+ });
});
diff --git a/test/schema.singlenestedpath.test.js b/test/schema.singlenestedpath.test.js
index 5365776f587..242fb3118a5 100644
--- a/test/schema.singlenestedpath.test.js
+++ b/test/schema.singlenestedpath.test.js
@@ -143,4 +143,42 @@ describe('SingleNestedPath', function() {
});
});
});
+
+ it('copies over `requiredValidator` (gh-8819)', function() {
+ const authorSchema = new mongoose.Schema({
+ name: String,
+ pseudonym: String
+ });
+
+ const bookSchema = new mongoose.Schema({
+ author: {
+ type: authorSchema,
+ required: true
+ }
+ });
+
+ const clone = bookSchema.clone();
+ assert.ok(clone.path('author').requiredValidator);
+ assert.strictEqual(clone.path('author').requiredValidator,
+ clone.path('author').validators[0].validator);
+ });
+
+ it('supports `set()` (gh-8883)', function() {
+ mongoose.deleteModel(/Test/);
+ mongoose.Schema.Types.Embedded.set('required', true);
+
+ const Model = mongoose.model('Test', mongoose.Schema({
+ nested: mongoose.Schema({
+ test: String
+ })
+ }));
+
+ const doc = new Model({});
+
+ const err = doc.validateSync();
+ assert.ok(err);
+ assert.ok(err.errors['nested']);
+
+ mongoose.Schema.Types.Embedded.set('required', false);
+ });
});
\ No newline at end of file
diff --git a/test/schema.test.js b/test/schema.test.js
index b01d5f7d248..ee340721164 100644
--- a/test/schema.test.js
+++ b/test/schema.test.js
@@ -1735,13 +1735,13 @@ describe('schema', function() {
done();
});
- it('methods named toString (gh-4551)', function(done) {
+ it('methods named toString (gh-4551)', function() {
this.schema.methods.toString = function() {
return 'test';
};
- // should not throw
- mongoose.model('gh4551', this.schema);
- done();
+ assert.doesNotThrow(() => {
+ mongoose.model('gh4551', this.schema);
+ });
});
it('handles default value = 0 (gh-4620)', function(done) {
@@ -1891,8 +1891,9 @@ describe('schema', function() {
const schema = new db.Schema({ name: MyType });
const otherSchema = schema.clone();
- // Should not throw
- otherSchema.add({ name2: MyType });
+ assert.doesNotThrow(function() {
+ otherSchema.add({ name2: MyType });
+ });
});
it('clones schema types (gh-7537)', function() {
@@ -2001,13 +2002,13 @@ describe('schema', function() {
});
assert.equal(schema.childSchemas.length, 2);
- assert.equal(schema.childSchemas[0].schema, schema1);
- assert.equal(schema.childSchemas[1].schema, schema2);
+ assert.strictEqual(schema.childSchemas[0].schema, schema1);
+ assert.strictEqual(schema.childSchemas[1].schema, schema2);
schema = schema.clone();
assert.equal(schema.childSchemas.length, 2);
- assert.equal(schema.childSchemas[0].schema, schema1);
- assert.equal(schema.childSchemas[1].schema, schema2);
+ assert.strictEqual(schema.childSchemas[0].schema, schema1);
+ assert.strictEqual(schema.childSchemas[1].schema, schema2);
done();
});
@@ -2425,6 +2426,35 @@ describe('schema', function() {
});
});
+ describe('mongoose.set(`strictQuery`, value); (gh-6658)', function() {
+ let strictQueryOriginalValue;
+
+ this.beforeEach(() => strictQueryOriginalValue = mongoose.get('strictQuery'));
+ this.afterEach(() => mongoose.set('strictQuery', strictQueryOriginalValue));
+
+ it('setting `strictQuery` on base sets strictQuery to schema (gh-6658)', function() {
+ // Arrange
+ mongoose.set('strictQuery', 'some value');
+
+ // Act
+ const schema = new Schema();
+
+ // Assert
+ assert.equal(schema.get('strictQuery'), 'some value');
+ });
+
+ it('`strictQuery` set on base gets overwritten by option set on schema (gh-6658)', function() {
+ // Arrange
+ mongoose.set('strictQuery', 'base option');
+
+ // Act
+ const schema = new Schema({}, { strictQuery: 'schema option' });
+
+ // Assert
+ assert.equal(schema.get('strictQuery'), 'schema option');
+ });
+ });
+
it('treats dotted paths with no parent as a nested path (gh-9020)', function() {
const customerSchema = new Schema({
'card.brand': String,
diff --git a/test/schematype.cast.test.js b/test/schematype.cast.test.js
index 2f89b53cd9c..b0ec4fe30cb 100644
--- a/test/schematype.cast.test.js
+++ b/test/schematype.cast.test.js
@@ -44,11 +44,11 @@ describe('SchemaType.cast() (gh-7045)', function() {
assert.equal(error.name, 'CastError');
}
- objectid.cast('000000000000000000000000'); // Should not throw
-
- // Base objectid shouldn't throw
- baseObjectId.cast('12charstring');
- baseObjectId.cast('000000000000000000000000');
+ assert.doesNotThrow(function() {
+ objectid.cast('000000000000000000000000');
+ baseObjectId.cast('12charstring');
+ baseObjectId.cast('000000000000000000000000');
+ });
assert.ok(threw);
});
@@ -119,8 +119,9 @@ describe('SchemaType.cast() (gh-7045)', function() {
assert.equal(error.name, 'CastError');
}
assert.ok(threw);
-
- b.cast(true); // Should not throw
+ assert.doesNotThrow(function() {
+ b.cast(true);
+ });
});
describe('string', function() {
@@ -131,7 +132,9 @@ describe('SchemaType.cast() (gh-7045)', function() {
});
const s = new Schema.Types.String();
- s.cast('short'); // Should not throw
+ assert.doesNotThrow(function() {
+ s.cast('short');
+ });
assert.throws(() => s.cast('wayyyy too long'), /CastError/);
});
@@ -140,7 +143,10 @@ describe('SchemaType.cast() (gh-7045)', function() {
Schema.Types.String.cast(false);
const s = new Schema.Types.String();
- s.cast('short'); // Should not throw
+
+ assert.doesNotThrow(function() {
+ s.cast('short');
+ });
assert.throws(() => s.cast(123), /CastError/);
});
@@ -154,8 +160,10 @@ describe('SchemaType.cast() (gh-7045)', function() {
});
const d = new Schema.Types.Date();
- d.cast('2018-06-01'); // Should not throw
- d.cast(new Date()); // Should not throw
+ assert.doesNotThrow(function() {
+ d.cast('2018-06-01');
+ d.cast(new Date());
+ });
assert.throws(() => d.cast(''), /CastError/);
});
@@ -164,7 +172,9 @@ describe('SchemaType.cast() (gh-7045)', function() {
Schema.Types.Date.cast(false);
const d = new Schema.Types.Date();
- d.cast(new Date()); // Should not throw
+ assert.doesNotThrow(function() {
+ d.cast(new Date());
+ });
assert.throws(() => d.cast('2018-06-01'), /CastError/);
});
@@ -178,7 +188,9 @@ describe('SchemaType.cast() (gh-7045)', function() {
});
const d = new Schema.Types.Decimal128();
- d.cast('1000'); // Should not throw
+ assert.doesNotThrow(function() {
+ d.cast('1000');
+ });
assert.throws(() => d.cast(1000), /CastError/);
});
@@ -190,7 +202,9 @@ describe('SchemaType.cast() (gh-7045)', function() {
assert.throws(() => d.cast('1000'), /CastError/);
assert.throws(() => d.cast(1000), /CastError/);
- d.cast(original.decimal128('1000')); // Should not throw
+ assert.doesNotThrow(function() {
+ d.cast(original.decimal128('1000'));
+ });
});
});
});
\ No newline at end of file