* Module dependencies.
*/
'use strict';
const start = require('./common');
const assert = require('assert');
const mongodb = require('mongodb');
const mongoose = require('./common').mongoose;
const MongooseArray = mongoose.Types.Array;
const Schema = mongoose.Schema;
* Test.
*/
describe('types array', function() {
let UserSchema;
let PetSchema;
let db;
before(function() {
UserSchema = new Schema({
name: String,
pets: [Schema.ObjectId]
});
PetSchema = new Schema({
name: String
});
db = start();
});
after(async function() {
await db.close();
});
beforeEach(() => db.deleteModel(/.*/));
afterEach(() => require('./util').clearTestData(db));
afterEach(() => require('./util').stopRemainingOps(db));
it('behaves and quacks like an Array', function(done) {
const a = new MongooseArray([]);
assert.ok(a instanceof Array);
assert.ok(a.isMongooseArray);
assert.equal(Array.isArray(a), true);
assert.deepEqual(a.$atomics().constructor, Object);
done();
});
it('is `deepEqual()` another array (gh-7700)', function(done) {
const Test = db.model('Test', new Schema({ arr: [String] }));
const doc = new Test({ arr: ['test'] });
assert.deepEqual(doc.arr, new MongooseArray(['test']));
assert.deepEqual(doc.arr, ['test']);
done();
});
describe('hasAtomics', function() {
it('does not throw', function(done) {
const b = new MongooseArray([12, 3, 4, 5]).filter(Boolean);
let threw = false;
try {
b.hasAtomics;
} catch {
threw = true;
}
assert.ok(!threw);
const a = new MongooseArray([67, 8]).filter(Boolean);
try {
a.push(3, 4);
} catch (err) {
console.error(err);
threw = true;
}
assert.ok(!threw);
done();
});
});
describe('indexOf()', function() {
it('works', async function() {
const User = db.model('User', UserSchema);
const Pet = db.model('Pet', PetSchema);
const tj = new User({ name: 'tj' });
const tobi = new Pet({ name: 'tobi' });
const loki = new Pet({ name: 'loki' });
const jane = new Pet({ name: 'jane' });
tj.pets.push(tobi);
tj.pets.push(loki);
tj.pets.push(jane);
await Pet.create([tobi, loki, jane]);
await tj.save();
const user = await User.findOne({ name: 'tj' });
assert.equal(user.pets.length, 3);
assert.equal(user.pets.indexOf(tobi.id), 0);
assert.equal(user.pets.indexOf(loki.id), 1);
assert.equal(user.pets.indexOf(jane.id), 2);
assert.equal(user.pets.indexOf(tobi._id), 0);
assert.equal(user.pets.indexOf(loki._id), 1);
assert.equal(user.pets.indexOf(jane._id), 2);
});
});
describe('includes()', function() {
it('works', async function() {
const User = db.model('User', UserSchema);
const Pet = db.model('Pet', PetSchema);
const tj = new User({ name: 'tj' });
const tobi = new Pet({ name: 'tobi' });
const loki = new Pet({ name: 'loki' });
const jane = new Pet({ name: 'jane' });
tj.pets.push(tobi);
tj.pets.push(loki);
tj.pets.push(jane);
await Pet.create([tobi, loki, jane]);
await tj.save();
const user = await User.findOne({ name: 'tj' });
assert.equal(user.pets.length, 3);
assert.equal(user.pets.includes(tobi.id), true);
assert.equal(user.pets.includes(loki.id), true);
assert.equal(user.pets.includes(jane.id), true);
assert.equal(user.pets.includes(tobi.id, 1), false);
assert.equal(user.pets.includes(loki.id, 1), true);
});
});
describe('push()', function() {
async function save(doc) {
await doc.save();
return doc.constructor.findById(doc._id);
}
it('works with numbers', async function() {
const N = db.model('Test', Schema({ arr: [Number] }));
const m = new N({ arr: [3, 4, 5, 6] });
let doc = await save(m);
assert.equal(doc.arr.length, 4);
doc.arr.push(8);
assert.strictEqual(8, doc.arr[doc.arr.length - 1]);
assert.strictEqual(8, doc.arr[4]);
doc = await save(doc);
assert.equal(doc.arr.length, 5);
assert.strictEqual(3, doc.arr[0]);
assert.strictEqual(4, doc.arr[1]);
assert.strictEqual(5, doc.arr[2]);
assert.strictEqual(6, doc.arr[3]);
assert.strictEqual(8, doc.arr[4]);
});
it('works with strings', async function() {
const S = db.model('Test', Schema({ arr: [String] }));
const m = new S({ arr: [3, 4, 5, 6] });
let doc = await save(m);
assert.equal(doc.arr.length, 4);
doc.arr.push(8);
assert.strictEqual('8', doc.arr[doc.arr.length - 1]);
assert.strictEqual('8', doc.arr[4]);
doc = await save(doc);
assert.equal(doc.arr.length, 5);
assert.strictEqual('3', doc.arr[0]);
assert.strictEqual('4', doc.arr[1]);
assert.strictEqual('5', doc.arr[2]);
assert.strictEqual('6', doc.arr[3]);
assert.strictEqual('8', doc.arr[4]);
});
it('works with buffers', async function() {
const B = db.model('Test', Schema({ arr: [Buffer] }));
const m = new B({ arr: [[0], Buffer.alloc(1)] });
let doc = await save(m);
assert.equal(doc.arr.length, 2);
assert.ok(doc.arr[0].isMongooseBuffer);
assert.ok(doc.arr[1].isMongooseBuffer);
doc.arr.push('nice');
assert.equal(doc.arr.length, 3);
assert.ok(doc.arr[2].isMongooseBuffer);
assert.strictEqual('nice', doc.arr[2].toString('utf8'));
doc = await save(doc);
assert.equal(doc.arr.length, 3);
assert.ok(doc.arr[0].isMongooseBuffer);
assert.ok(doc.arr[1].isMongooseBuffer);
assert.ok(doc.arr[2].isMongooseBuffer);
assert.strictEqual('\u0000', doc.arr[0].toString());
assert.strictEqual('nice', doc.arr[2].toString());
});
it('works with mixed', async function() {
const M = db.model('Test', Schema({ arr: [] }));
const m = new M({ arr: [3, { x: 1 }, 'yes', [5]] });
let doc = await save(m);
assert.equal(doc.arr.length, 4);
doc.arr.push(null);
assert.equal(doc.arr.length, 5);
assert.strictEqual(null, doc.arr[4]);
doc = await save(doc);
assert.equal(doc.arr.length, 5);
assert.strictEqual(3, doc.arr[0]);
assert.strictEqual(1, doc.arr[1].x);
assert.strictEqual('yes', doc.arr[2]);
assert.ok(Array.isArray(doc.arr[3]));
assert.strictEqual(5, doc.arr[3][0]);
assert.strictEqual(null, doc.arr[4]);
doc.arr.push(Infinity);
assert.equal(doc.arr.length, 6);
assert.strictEqual(Infinity, doc.arr[5]);
doc.arr.push(Buffer.alloc(0));
assert.equal(doc.arr.length, 7);
assert.strictEqual('', doc.arr[6].toString());
doc = await save(doc);
});
it('works with sub-docs', async function() {
const D = db.model('Test', Schema({ arr: [{ name: String }] }));
const m = new D({ arr: [{ name: 'aaron' }, { name: 'moombahton ' }] });
let doc = await save(m);
assert.equal(doc.arr.length, 2);
doc.arr.push({ name: 'Restrepo' });
assert.equal(doc.arr.length, 3);
assert.equal(doc.arr[2].name, 'Restrepo');
doc = await save(doc);
assert.equal(doc.arr.length, 3);
assert.equal(doc.arr[0].name, 'aaron');
assert.equal(doc.arr[1].name, 'moombahton ');
assert.equal(doc.arr[2].name, 'Restrepo');
});
it('applies setters (gh-3032)', async function() {
const ST = db.model('Test', Schema({
arr: [{
type: String,
lowercase: true
}]
}));
const m = new ST({ arr: ['ONE', 'TWO'] });
let doc = await save(m);
assert.equal(doc.arr.length, 2);
doc.arr.push('THREE');
assert.strictEqual('one', doc.arr[0]);
assert.strictEqual('two', doc.arr[1]);
assert.strictEqual('three', doc.arr[2]);
doc = await save(doc);
assert.equal(doc.arr.length, 3);
assert.strictEqual('one', doc.arr[0]);
assert.strictEqual('two', doc.arr[1]);
assert.strictEqual('three', doc.arr[2]);
});
});
describe('splice()', function() {
it('works', async function() {
const schema = new Schema({ numbers: [Number] });
const A = db.model('Test', schema);
const a = new A({ numbers: [4, 5, 6, 7] });
await a.save();
let doc = await A.findById(a._id);
const removed = doc.numbers.splice(1, 1, '10');
assert.deepEqual(removed, [5]);
assert.equal(typeof doc.numbers[1], 'number');
assert.deepStrictEqual(doc.numbers.toObject(), [4, 10, 6, 7]);
await doc.save();
doc = await A.findById(a._id);
assert.deepStrictEqual(doc.numbers.toObject(), [4, 10, 6, 7]);
});
it('on embedded docs', async function() {
const schema = new Schema({ types: [new Schema({ type: String })] });
const A = db.model('Test', schema);
const a = new A({ types: [{ type: 'bird' }, { type: 'boy' }, { type: 'frog' }, { type: 'cloud' }] });
await a.save();
let doc = await A.findById(a._id);
doc.types.$pop();
const removed = doc.types.splice(1, 1);
assert.equal(removed.length, 1);
assert.equal(removed[0].type, 'boy');
let obj = doc.types.toObject();
assert.equal(obj[0].type, 'bird');
assert.equal(obj[1].type, 'frog');
await doc.save();
doc = await A.findById(a._id);
obj = doc.types.toObject();
assert.equal(obj[0].type, 'bird');
assert.equal(obj[1].type, 'frog');
});
});
describe('unshift()', function() {
it('works', async function() {
const schema = new Schema({
types: [new Schema({ type: String })],
nums: [Number],
strs: [String]
});
const A = db.model('Test', schema);
const a = new A({
types: [{ type: 'bird' }, { type: 'boy' }, { type: 'frog' }, { type: 'cloud' }],
nums: [1, 2, 3],
strs: 'one two three'.split(' ')
});
await a.save();
let doc = await A.findById(a._id);
const tlen = doc.types.unshift({ type: 'tree' });
const nlen = doc.nums.unshift(0);
const slen = doc.strs.unshift('zero');
assert.equal(tlen, 5);
assert.equal(nlen, 4);
assert.equal(slen, 4);
doc.types.push({ type: 'worm' });
let obj = doc.types.toObject();
assert.equal(obj[0].type, 'tree');
assert.equal(obj[1].type, 'bird');
assert.equal(obj[2].type, 'boy');
assert.equal(obj[3].type, 'frog');
assert.equal(obj[4].type, 'cloud');
assert.equal(obj[5].type, 'worm');
obj = doc.nums.toObject();
assert.equal(obj[0].valueOf(), 0);
assert.equal(obj[1].valueOf(), 1);
assert.equal(obj[2].valueOf(), 2);
assert.equal(obj[3].valueOf(), 3);
obj = doc.strs.toObject();
assert.equal(obj[0], 'zero');
assert.equal(obj[1], 'one');
assert.equal(obj[2], 'two');
assert.equal(obj[3], 'three');
await doc.save();
doc = await A.findById(a._id);
obj = doc.types.toObject();
assert.equal(obj[0].type, 'tree');
assert.equal(obj[1].type, 'bird');
assert.equal(obj[2].type, 'boy');
assert.equal(obj[3].type, 'frog');
assert.equal(obj[4].type, 'cloud');
assert.equal(obj[5].type, 'worm');
obj = doc.nums.toObject();
assert.equal(obj[0].valueOf(), 0);
assert.equal(obj[1].valueOf(), 1);
assert.equal(obj[2].valueOf(), 2);
assert.equal(obj[3].valueOf(), 3);
obj = doc.strs.toObject();
assert.equal(obj[0], 'zero');
assert.equal(obj[1], 'one');
assert.equal(obj[2], 'two');
assert.equal(obj[3], 'three');
});
it('applies setters (gh-3032)', async function() {
const ST = db.model('Test', Schema({
arr: [{
type: String,
lowercase: true
}]
}));
const m = new ST({ arr: ['ONE', 'TWO'] });
const doc = await m.save();
assert.equal(doc.arr.length, 2);
doc.arr.unshift('THREE');
assert.strictEqual('three', doc.arr[0]);
assert.strictEqual('one', doc.arr[1]);
assert.strictEqual('two', doc.arr[2]);
await doc.save();
assert.equal(doc.arr.length, 3);
assert.strictEqual('three', doc.arr[0]);
assert.strictEqual('one', doc.arr[1]);
assert.strictEqual('two', doc.arr[2]);
});
});
describe('shift()', function() {
it('works', async function() {
const schema = new Schema({
types: [new Schema({ type: String })],
nums: [Number],
strs: [String]
});
const A = db.model('Test', schema);
const a = new A({
types: [{ type: 'bird' }, { type: 'boy' }, { type: 'frog' }, { type: 'cloud' }],
nums: [1, 2, 3],
strs: 'one two three'.split(' ')
});
await a.save();
let doc = await A.findById(a._id);
const t = doc.types.shift();
const n = doc.nums.shift();
const s = doc.strs.shift();
assert.equal(t.type, 'bird');
assert.equal(n, 1);
assert.equal(s, 'one');
let obj = doc.types.toObject();
assert.equal(obj[0].type, 'boy');
assert.equal(obj[1].type, 'frog');
assert.equal(obj[2].type, 'cloud');
doc.nums.push(4);
obj = doc.nums.toObject();
assert.equal(obj[0].valueOf(), 2);
assert.equal(obj[1].valueOf(), 3);
assert.equal(obj[2].valueOf(), 4);
obj = doc.strs.toObject();
assert.equal(obj[0], 'two');
assert.equal(obj[1], 'three');
await doc.save();
doc = await A.findById(a._id);
obj = doc.types.toObject();
assert.equal(obj[0].type, 'boy');
assert.equal(obj[1].type, 'frog');
assert.equal(obj[2].type, 'cloud');
obj = doc.nums.toObject();
assert.equal(obj[0].valueOf(), 2);
assert.equal(obj[1].valueOf(), 3);
assert.equal(obj[2].valueOf(), 4);
obj = doc.strs.toObject();
assert.equal(obj[0], 'two');
assert.equal(obj[1], 'three');
});
});
describe('$shift', function() {
it('works', async function() {
const painting = new Schema({ colors: [] });
const Painting = db.model('Test', painting);
const p = new Painting({ colors: ['blue', 'green', 'yellow'] });
await p.save();
let doc = await Painting.findById(p);
assert.equal(doc.colors.length, 3);
let color = doc.colors.$shift();
assert.equal(doc.colors.length, 2);
assert.equal(color, 'blue');
color = doc.colors.$shift();
assert.equal(color, undefined);
assert.equal(doc.colors.length, 2);
await doc.save();
color = doc.colors.$shift();
assert.equal(doc.colors.length, 1);
assert.equal(color, 'green');
await doc.save();
doc = await Painting.findById(doc);
assert.equal(doc.colors.length, 1);
assert.equal(doc.colors[0], 'yellow');
});
});
describe('pop()', function() {
it('works', async function() {
const schema = new Schema({
types: [new Schema({ type: String })],
nums: [Number],
strs: [String]
});
const A = db.model('Test', schema);
const a = new A({
types: [{ type: 'bird' }, { type: 'boy' }, { type: 'frog' }, { type: 'cloud' }],
nums: [1, 2, 3],
strs: 'one two three'.split(' ')
});
await a.save();
let doc = await A.findById(a._id);
const t = doc.types.pop();
const n = doc.nums.pop();
const s = doc.strs.pop();
assert.equal(t.type, 'cloud');
assert.equal(n, 3);
assert.equal(s, 'three');
let obj = doc.types.toObject();
assert.equal(obj[0].type, 'bird');
assert.equal(obj[1].type, 'boy');
assert.equal(obj[2].type, 'frog');
doc.nums.push(4);
obj = doc.nums.toObject();
assert.equal(obj[0].valueOf(), 1);
assert.equal(obj[1].valueOf(), 2);
assert.equal(obj[2].valueOf(), 4);
obj = doc.strs.toObject();
assert.equal(obj[0], 'one');
assert.equal(obj[1], 'two');
await doc.save();
doc = await A.findById(a._id);
obj = doc.types.toObject();
assert.equal(obj[0].type, 'bird');
assert.equal(obj[1].type, 'boy');
assert.equal(obj[2].type, 'frog');
obj = doc.nums.toObject();
assert.equal(obj[0].valueOf(), 1);
assert.equal(obj[1].valueOf(), 2);
assert.equal(obj[2].valueOf(), 4);
obj = doc.strs.toObject();
assert.equal(obj[0], 'one');
assert.equal(obj[1], 'two');
});
});
describe('pull()', function() {
it('works', async function() {
const catschema = new Schema({ name: String });
const Cat = db.model('Cat', catschema);
const schema = new Schema({
a: [{ type: Schema.ObjectId, ref: 'Cat' }]
});
const A = db.model('Test', schema);
const cat = new Cat({ name: 'peanut' });
await cat.save();
const a = new A({ a: [cat._id] });
await a.save();
const doc = await A.findById(a);
assert.equal(doc.a.length, 1);
doc.a.pull(cat.id);
assert.equal(doc.a.length, 0);
assert.ok(doc.getChanges().$pullAll.a);
});
it('registers $pull atomic if pulling from middle (gh-14502)', async function() {
const schema = new Schema({
a: [{ type: Schema.ObjectId, ref: 'Cat' }]
});
const A = db.model('Test', schema);
const oid1 = new mongoose.Types.ObjectId();
const oid2 = new mongoose.Types.ObjectId();
const oid3 = new mongoose.Types.ObjectId();
const a = new A({ a: [oid1, oid2, oid3] });
await a.save();
const doc = await A.findById(a);
assert.equal(doc.a.length, 3);
doc.a.pull(oid2);
assert.equal(doc.a.length, 2);
assert.ok(doc.getChanges().$pullAll.a);
});
it('handles pulling with no _id (gh-3341)', async function() {
const personSchema = new Schema({
name: String,
role: String
}, { _id: false });
const bandSchema = new Schema({
name: String,
members: [personSchema]
});
const Band = db.model('Test', bandSchema);
let gnr = new Band({
name: 'Guns N\' Roses',
members: [
{ name: 'Axl', role: 'Lead Singer' },
{ name: 'Slash', role: 'Guitar' },
{ name: 'Izzy', role: 'Guitar' },
{ name: 'Duff', role: 'Bass' },
{ name: 'Adler', role: 'Drums' }
]
});
await gnr.save();
gnr.members.pull({ name: 'Slash', role: 'Guitar' });
await gnr.save();
assert.equal(gnr.members.length, 4);
assert.equal(gnr.members[0].name, 'Axl');
assert.equal(gnr.members[1].name, 'Izzy');
assert.equal(gnr.members[2].name, 'Duff');
assert.equal(gnr.members[3].name, 'Adler');
gnr = await Band.findById(gnr._id);
assert.equal(gnr.members.length, 4);
assert.equal(gnr.members[0].name, 'Axl');
assert.equal(gnr.members[1].name, 'Izzy');
assert.equal(gnr.members[2].name, 'Duff');
assert.equal(gnr.members[3].name, 'Adler');
});
it('properly works with undefined', async function() {
const catschema = new Schema({ name: String, colors: [{ hex: String }] });
const Cat = db.model('Test', catschema);
const cat = new Cat({ name: 'peanut', colors: [
{ hex: '#FFF' }, { hex: '#000' }, null
] });
await cat.save();
cat.colors.pull(undefined);
assert.equal(cat.colors.length, 2);
assert.equal(cat.colors[0].hex, '#FFF');
assert.equal(cat.colors[1].hex, '#000');
await cat.save();
const doc = await Cat.findById(cat._id);
assert.equal(doc.colors.length, 2);
assert.equal(doc.colors[0].hex, '#FFF');
assert.equal(doc.colors[1].hex, '#000');
});
it('avoids adding default paths to query filter (gh-12294)', async function() {
const catschema = new Schema({
name: String,
colors: [{
_id: false,
hex: { type: String, default: '#ffffff' },
properties: {
hue: { type: Number, default: 0 }
},
name: String
}]
});
const Cat = db.model('Test', catschema);
const cat = new Cat({});
cat.init({
name: 'Garfield',
colors: [{ name: 'Orange' }]
});
cat.colors.pull({ name: 'Orange' });
assert.deepStrictEqual(cat.colors.$__getAtomics(), [[
'$pull',
{ $or: [{ name: 'Orange' }] }
]]);
});
it('avoids adding default paths to query filter with _id (gh-12294)', async function() {
const catschema = new Schema({
name: String,
colors: [{
hex: { type: String, default: '#ffffff' },
name: String
}]
});
const Cat = db.model('Test', catschema);
const cat = new Cat({});
cat.init({
name: 'Garfield',
colors: [{ name: 'Orange' }]
});
cat.colors.pull({ name: 'Orange' });
assert.deepStrictEqual(cat.colors.$__getAtomics(), [[
'$pull',
{ $or: [{ name: 'Orange' }] }
]]);
});
});
describe('$pop()', function() {
it('works', async function() {
const painting = new Schema({ colors: [] });
const Painting = db.model('Test', painting);
const p = new Painting({ colors: ['blue', 'green', 'yellow'] });
await p.save();
let doc = await Painting.findById(p);
assert.equal(doc.colors.length, 3);
let color = doc.colors.$pop();
assert.equal(doc.colors.length, 2);
assert.equal(color, 'yellow');
color = doc.colors.$pop();
assert.equal(color, undefined);
assert.equal(doc.colors.length, 2);
assert.ok(!('$set' in doc.colors.$atomics()), 'invalid $atomic op used');
await doc.save();
color = doc.colors.$pop();
assert.equal(doc.colors.length, 1);
assert.equal(color, 'green');
await doc.save();
doc = await Painting.findById(doc);
assert.equal(doc.colors.length, 1);
assert.equal(doc.colors[0], 'blue');
});
});
describe('addToSet()', function() {
it('works', async function() {
const e = new Schema({ name: String, arr: [] });
const schema = new Schema({
num: [Number],
str: [String],
doc: [e],
date: [Date],
id: [Schema.ObjectId]
});
const M = db.model('Test', schema);
let m = new M();
m.num.push(1, 2, 3);
m.str.push('one', 'two', 'tres');
m.doc.push({ name: 'Dubstep', arr: [1] }, { name: 'Polka', arr: [{ x: 3 }] });
const d1 = new Date();
const d2 = new Date(+d1 + 60000);
const d3 = new Date(+d1 + 30000);
const d4 = new Date(+d1 + 20000);
const d5 = new Date(+d1 + 90000);
const d6 = new Date(+d1 + 10000);
m.date.push(d1, d2);
const id1 = new mongoose.Types.ObjectId();
const id2 = new mongoose.Types.ObjectId();
const id3 = new mongoose.Types.ObjectId();
const id4 = new mongoose.Types.ObjectId();
const id5 = new mongoose.Types.ObjectId();
const id6 = new mongoose.Types.ObjectId();
m.id.push(id1, id2);
m.num.addToSet(3, 4, 5);
assert.equal(m.num.length, 5);
m.str.addToSet('four', 'five', 'two');
assert.equal(m.str.length, 5);
m.id.addToSet(id2, id3);
assert.equal(m.id.length, 3);
m.doc.addToSet(m.doc[0]);
assert.equal(m.doc.length, 2);
m.doc.addToSet({ name: 'Waltz', arr: [1] }, m.doc[0]);
assert.equal(m.doc.length, 3);
assert.equal(m.date.length, 2);
m.date.addToSet(d1);
assert.equal(m.date.length, 2);
m.date.addToSet(d3);
assert.equal(m.date.length, 3);
await m.save();
m = await M.findById(m);
assert.equal(m.num.length, 5);
assert.ok(~m.num.indexOf(1));
assert.ok(~m.num.indexOf(2));
assert.ok(~m.num.indexOf(3));
assert.ok(~m.num.indexOf(4));
assert.ok(~m.num.indexOf(5));
assert.equal(m.str.length, 5);
assert.ok(~m.str.indexOf('one'));
assert.ok(~m.str.indexOf('two'));
assert.ok(~m.str.indexOf('tres'));
assert.ok(~m.str.indexOf('four'));
assert.ok(~m.str.indexOf('five'));
assert.equal(m.id.length, 3);
assert.ok(m.id.find(id => id.toString() === id1.toString()));
assert.ok(m.id.find(id => id.toString() === id2.toString()));
assert.ok(m.id.find(id => id.toString() === id3.toString()));
assert.equal(m.date.length, 3);
assert.ok(~m.date.indexOf(d1.toString()));
assert.ok(~m.date.indexOf(d2.toString()));
assert.ok(~m.date.indexOf(d3.toString()));
assert.equal(m.doc.length, 3);
assert.ok(m.doc.some(function(v) {
return v.name === 'Waltz';
}));
assert.ok(m.doc.some(function(v) {
return v.name === 'Dubstep';
}));
assert.ok(m.doc.some(function(v) {
return v.name === 'Polka';
}));
m.num.addToSet(3, 4, 5, 6);
assert.equal(m.num.length, 6);
m.str.addToSet('four', 'five', 'two', 'six');
assert.equal(m.str.length, 6);
m.id.addToSet(id2, id3, id4);
assert.equal(m.id.length, 4);
m.date.addToSet(d1, d3, d4);
assert.equal(m.date.length, 4);
m.doc.addToSet(m.doc[0], { name: '8bit' });
assert.equal(m.doc.length, 4);
await m.save();
m = await M.findById(m);
assert.equal(m.num.length, 6);
assert.ok(~m.num.indexOf(1));
assert.ok(~m.num.indexOf(2));
assert.ok(~m.num.indexOf(3));
assert.ok(~m.num.indexOf(4));
assert.ok(~m.num.indexOf(5));
assert.ok(~m.num.indexOf(6));
assert.equal(m.str.length, 6);
assert.ok(~m.str.indexOf('one'));
assert.ok(~m.str.indexOf('two'));
assert.ok(~m.str.indexOf('tres'));
assert.ok(~m.str.indexOf('four'));
assert.ok(~m.str.indexOf('five'));
assert.ok(~m.str.indexOf('six'));
assert.equal(m.id.length, 4);
assert.ok(m.id.find(id => id.toString() === id1.toString()));
assert.ok(m.id.find(id => id.toString() === id2.toString()));
assert.ok(m.id.find(id => id.toString() === id3.toString()));
assert.ok(m.id.find(id => id.toString() === id4.toString()));
assert.equal(m.date.length, 4);
assert.ok(~m.date.indexOf(d1.toString()));
assert.ok(~m.date.indexOf(d2.toString()));
assert.ok(~m.date.indexOf(d3.toString()));
assert.ok(~m.date.indexOf(d4.toString()));
assert.equal(m.doc.length, 4);
assert.ok(m.doc.some(function(v) {
return v.name === 'Waltz';
}));
assert.ok(m.doc.some(function(v) {
return v.name === 'Dubstep';
}));
assert.ok(m.doc.some(function(v) {
return v.name === 'Polka';
}));
assert.ok(m.doc.some(function(v) {
return v.name === '8bit';
}));
m.num.addToSet(7, 8);
assert.equal(m.num.length, 8);
m.str.addToSet('seven', 'eight');
assert.equal(m.str.length, 8);
m.id.addToSet(id5, id6);
assert.equal(m.id.length, 6);
m.date.addToSet(d5, d6);
assert.equal(m.date.length, 6);
m.doc.addToSet(m.doc[1], { name: 'BigBeat' }, { name: 'Funk' });
assert.equal(m.doc.length, 6);
await m.save();
m = await M.findById(m);
assert.equal(m.num.length, 8);
assert.ok(~m.num.indexOf(1));
assert.ok(~m.num.indexOf(2));
assert.ok(~m.num.indexOf(3));
assert.ok(~m.num.indexOf(4));
assert.ok(~m.num.indexOf(5));
assert.ok(~m.num.indexOf(6));
assert.ok(~m.num.indexOf(7));
assert.ok(~m.num.indexOf(8));
assert.equal(m.str.length, 8);
assert.ok(~m.str.indexOf('one'));
assert.ok(~m.str.indexOf('two'));
assert.ok(~m.str.indexOf('tres'));
assert.ok(~m.str.indexOf('four'));
assert.ok(~m.str.indexOf('five'));
assert.ok(~m.str.indexOf('six'));
assert.ok(~m.str.indexOf('seven'));
assert.ok(~m.str.indexOf('eight'));
assert.equal(m.id.length, 6);
assert.ok(~m.id.indexOf(id1));
assert.ok(~m.id.indexOf(id2));
assert.ok(~m.id.indexOf(id3));
assert.ok(~m.id.indexOf(id4));
assert.ok(~m.id.indexOf(id5));
assert.ok(~m.id.indexOf(id6));
assert.equal(m.date.length, 6);
assert.ok(~m.date.indexOf(d1.toString()));
assert.ok(~m.date.indexOf(d2.toString()));
assert.ok(~m.date.indexOf(d3.toString()));
assert.ok(~m.date.indexOf(d4.toString()));
assert.ok(~m.date.indexOf(d5.toString()));
assert.ok(~m.date.indexOf(d6.toString()));
assert.equal(m.doc.length, 6);
assert.ok(m.doc.some(function(v) {
return v.name === 'Waltz';
}));
assert.ok(m.doc.some(function(v) {
return v.name === 'Dubstep';
}));
assert.ok(m.doc.some(function(v) {
return v.name === 'Polka';
}));
assert.ok(m.doc.some(function(v) {
return v.name === '8bit';
}));
assert.ok(m.doc.some(function(v) {
return v.name === 'BigBeat';
}));
assert.ok(m.doc.some(function(v) {
return v.name === 'Funk';
}));
});
it('handles sub-documents that do not have an _id gh-1973', async function() {
const e = new Schema({ name: String, arr: [] }, { _id: false });
const schema = new Schema({
doc: [e]
});
const M = db.model('Test', schema);
const m = new M();
m.doc.addToSet({ name: 'Rap' });
await m.save();
assert.equal(m.doc.length, 1);
assert.equal(m.doc[0].name, 'Rap');
m.doc.addToSet({ name: 'House' });
assert.equal(m.doc.length, 2);
await m.save();
assert.equal(m.doc.length, 2);
assert.ok(m.doc.some(function(v) {
return v.name === 'Rap';
}));
assert.ok(m.doc.some(function(v) {
return v.name === 'House';
}));
});
it('applies setters (gh-3032)', async function() {
const ST = db.model('Test', Schema({
arr: [{
type: String,
lowercase: true
}]
}));
const m = new ST({ arr: ['ONE', 'TWO'] });
const doc = await m.save();
assert.equal(doc.arr.length, 2);
doc.arr.addToSet('THREE');
assert.strictEqual('one', doc.arr[0]);
assert.strictEqual('two', doc.arr[1]);
assert.strictEqual('three', doc.arr[2]);
await doc.save();
assert.equal(doc.arr.length, 3);
assert.strictEqual('one', doc.arr[0]);
assert.strictEqual('two', doc.arr[1]);
assert.strictEqual('three', doc.arr[2]);
});
});
describe('options', function() {
let arrOptions;
let docArrOptions;
beforeEach(function() {
arrOptions = Object.assign({}, mongoose.Schema.Types.Array.options);
docArrOptions = Object.assign({}, mongoose.Schema.Types.DocumentArray.options);
mongoose.Schema.Types.Array.options.castNonArrays = false;
mongoose.Schema.Types.DocumentArray.options.castNonArrays = false;
});
afterEach(function() {
mongoose.Schema.Types.Array.options = arrOptions;
mongoose.Schema.Types.DocumentArray.options = docArrOptions;
});
it('castNonArrays (gh-7371) (gh-7479)', function() {
const schema = new Schema({ arr: [String], docArr: [{ name: String }] });
const Model = db.model('Test', schema);
let doc = new Model({ arr: 'fail', docArr: { name: 'fail' } });
assert.ok(doc.validateSync().errors);
assert.equal(doc.validateSync().errors['arr'].name, 'CastError');
assert.equal(doc.validateSync().errors['docArr'].name, 'CastError');
doc = new Model({ arr: ['good'] });
assert.ifError(doc.validateSync());
doc.arr.push('foo');
assert.ifError(doc.validateSync());
assert.deepEqual(doc.arr.toObject(), ['good', 'foo']);
const bothSchema = new Schema({ arr: { castNonArrays: true, type: [String] }, docArr: { castNonArrays: true, type: [{ name: String }] } });
const bothModel = db.model('Test2', bothSchema);
let bothdoc = new bothModel({ arr: 'fail', docArr: { name: 'fail' } });
assert.ifError(doc.validateSync());
bothdoc = new bothModel({ arr: ['good'] });
assert.ifError(bothdoc.validateSync());
bothdoc.arr.push('foo');
assert.ifError(bothdoc.validateSync());
assert.deepEqual(bothdoc.arr.toObject(), ['good', 'foo']);
return Promise.resolve();
});
it('works with $addToSet and $push (gh-7479)', async function() {
const schema = new Schema({
arr: [mongoose.Schema.Types.ObjectId]
});
const Model = db.model('Test', schema);
await Model.create({ arr: [] });
const oid = new mongoose.Types.ObjectId();
await Model.updateMany({}, {
$addToSet: { arr: oid }
});
let raw = await Model.collection.findOne();
assert.equal(raw.arr[0].toHexString(), oid.toHexString());
await Model.updateMany({}, {
$push: { arr: oid }
});
raw = await Model.collection.findOne();
assert.equal(raw.arr[1].toHexString(), oid.toHexString());
});
});
describe('nonAtomicPush()', function() {
it('works', async function() {
const U = db.model('User', UserSchema);
const ID = mongoose.Types.ObjectId;
const u = new U({ name: 'banana', pets: [new ID()] });
assert.equal(u.pets.length, 1);
u.pets.nonAtomicPush(new ID());
assert.equal(u.pets.length, 2);
await u.save();
await U.findById(u._id);
assert.equal(u.pets.length, 2);
const id0 = u.pets[0];
const id1 = u.pets[1];
const id2 = new ID();
u.pets.pull(id0);
u.pets.nonAtomicPush(id2);
assert.equal(u.pets.length, 2);
assert.equal(u.pets[0].toString(), id1.toString());
assert.equal(u.pets[1].toString(), id2.toString());
await u.save();
await U.findById(u._id);
assert.equal(u.pets.length, 2);
assert.equal(u.pets[0].toString(), id1.toString());
assert.equal(u.pets[1].toString(), id2.toString());
});
});
describe('sort()', function() {
it('order should be saved', async function() {
const M = db.model('Test', new Schema({ x: [Number] }));
let m = new M({ x: [1, 4, 3, 2] });
await m.save();
m = await M.findById(m);
assert.equal(m.x[0], 1);
assert.equal(m.x[1], 4);
assert.equal(m.x[2], 3);
assert.equal(m.x[3], 2);
m.x.sort();
await m.save();
m = await M.findById(m);
assert.equal(m.x[0], 1);
assert.equal(m.x[1], 2);
assert.equal(m.x[2], 3);
assert.equal(m.x[3], 4);
m.x.sort(function(a, b) {
return b - a;
});
await m.save();
m = await M.findById(m);
assert.equal(m.x[0], 4);
assert.equal(m.x[1], 3);
assert.equal(m.x[2], 2);
assert.equal(m.x[3], 1);
});
});
describe('set()', function() {
async function save(doc) {
await doc.save();
return doc.constructor.findById(doc._id);
}
it('works combined with other ops', async function() {
const N = db.model('Test', Schema({ arr: [Number] }));
const m = new N({ arr: [3, 4, 5, 6] });
let doc = await save(m);
assert.equal(doc.arr.length, 4);
doc.arr.push(20);
doc.arr.set(2, 10);
assert.equal(doc.arr.length, 5);
assert.equal(doc.arr[2], 10);
assert.equal(doc.arr[4], 20);
doc = await save(doc);
assert.equal(doc.arr.length, 5);
assert.equal(doc.arr[0], 3);
assert.equal(doc.arr[1], 4);
assert.equal(doc.arr[2], 10);
assert.equal(doc.arr[3], 6);
assert.equal(doc.arr[4], 20);
doc.arr.$pop();
assert.equal(doc.arr.length, 4);
doc.arr.set(4, 99);
assert.equal(doc.arr.length, 5);
assert.equal(doc.arr[4], 99);
doc.arr.remove(10);
assert.equal(doc.arr.length, 4);
assert.equal(doc.arr[0], 3);
assert.equal(doc.arr[1], 4);
assert.equal(doc.arr[2], 6);
assert.equal(doc.arr[3], 99);
doc = await save(doc);
assert.equal(doc.arr.length, 4);
assert.equal(doc.arr[0], 3);
assert.equal(doc.arr[1], 4);
assert.equal(doc.arr[2], 6);
assert.equal(doc.arr[3], 99);
});
it('works with numbers', async function() {
const N = db.model('Test', Schema({ arr: [Number] }));
const m = new N({ arr: [3, 4, 5, 6] });
let doc = await save(m);
assert.equal(doc.arr.length, 4);
doc.arr.set(2, 10);
assert.equal(doc.arr.length, 4);
assert.equal(doc.arr[2], 10);
doc.arr.set(doc.arr.length, 11);
assert.equal(doc.arr.length, 5);
assert.equal(doc.arr[4], 11);
doc = await save(doc);
assert.equal(doc.arr.length, 5);
assert.equal(doc.arr[0], 3);
assert.equal(doc.arr[1], 4);
assert.equal(doc.arr[2], 10);
assert.equal(doc.arr[3], 6);
assert.equal(doc.arr[4], 11);
doc.arr.set(8, '1');
assert.equal(doc.arr.length, 9);
assert.strictEqual(1, doc.arr[8]);
assert.equal(doc.arr[7], undefined);
doc = await save(doc);
assert.equal(doc.arr.length, 9);
assert.equal(doc.arr[0], 3);
assert.equal(doc.arr[1], 4);
assert.equal(doc.arr[2], 10);
assert.equal(doc.arr[3], 6);
assert.equal(doc.arr[4], 11);
assert.equal(doc.arr[5], null);
assert.equal(doc.arr[6], null);
assert.equal(doc.arr[7], null);
assert.strictEqual(1, doc.arr[8]);
});
it('works with strings', async function() {
const S = db.model('Test', Schema({ arr: [String] }));
const m = new S({ arr: [3, 4, 5, 6] });
let doc = await save(m);
assert.equal(doc.arr.length, '4');
doc.arr[2] = 10;
assert.equal(doc.arr.length, 4);
assert.equal(doc.arr[2], '10');
doc.arr.set(doc.arr.length, '11');
assert.equal(doc.arr.length, 5);
assert.equal(doc.arr[4], '11');
doc = await save(doc);
assert.equal(doc.arr.length, 5);
assert.equal(doc.arr[0], '3');
assert.equal(doc.arr[1], '4');
assert.equal(doc.arr[2], '10');
assert.equal(doc.arr[3], '6');
assert.equal(doc.arr[4], '11');
doc.arr.set(8, 'yo');
assert.equal(doc.arr.length, 9);
assert.strictEqual('yo', doc.arr[8]);
assert.equal(doc.arr[7], undefined);
doc = await save(doc);
assert.equal(doc.arr.length, '9');
assert.equal(doc.arr[0], '3');
assert.equal(doc.arr[1], '4');
assert.equal(doc.arr[2], '10');
assert.equal(doc.arr[3], '6');
assert.equal(doc.arr[4], '11');
assert.equal(doc.arr[5], null);
assert.equal(doc.arr[6], null);
assert.equal(doc.arr[7], null);
assert.strictEqual('yo', doc.arr[8]);
});
it('works with buffers', async function() {
const B = db.model('Test', Schema({ arr: [Buffer] }));
const m = new B({ arr: [[0], Buffer.alloc(1)] });
let doc = await save(m);
assert.equal(doc.arr.length, 2);
assert.ok(doc.arr[0].isMongooseBuffer);
assert.ok(doc.arr[1].isMongooseBuffer);
doc.arr.set(1, 'nice');
assert.equal(doc.arr.length, 2);
assert.ok(doc.arr[1].isMongooseBuffer);
assert.equal(doc.arr[1].toString('utf8'), 'nice');
doc.arr.set(doc.arr.length, [11]);
assert.equal(doc.arr.length, 3);
assert.equal(doc.arr[2][0], 11);
doc = await save(doc);
assert.equal(doc.arr.length, 3);
assert.ok(doc.arr[0].isMongooseBuffer);
assert.ok(doc.arr[1].isMongooseBuffer);
assert.ok(doc.arr[2].isMongooseBuffer);
assert.equal(doc.arr[0].toString(), '\u0000');
assert.equal(doc.arr[1].toString(), 'nice');
assert.equal(doc.arr[2][0], 11);
});
it('works with mixed', async function() {
const M = db.model('Test', Schema({ arr: [] }));
const m = new M({ arr: [3, { x: 1 }, 'yes', [5]] });
let doc = await save(m);
assert.equal(doc.arr.length, 4);
doc.arr.set(2, null);
assert.equal(doc.arr.length, 4);
assert.equal(doc.arr[2], null);
doc.arr.set(doc.arr.length, 'last');
assert.equal(doc.arr.length, 5);
assert.equal(doc.arr[4], 'last');
doc = await save(doc);
assert.equal(doc.arr.length, 5);
assert.equal(doc.arr[0], 3);
assert.strictEqual(1, doc.arr[1].x);
assert.equal(doc.arr[2], null);
assert.ok(Array.isArray(doc.arr[3]));
assert.equal(doc.arr[3][0], 5);
assert.equal(doc.arr[4], 'last');
doc.arr.set(8, Infinity);
assert.equal(doc.arr.length, 9);
assert.strictEqual(Infinity, doc.arr[8]);
assert.equal(doc.arr[7], undefined);
doc.arr.push(Buffer.alloc(0));
assert.equal(doc.arr[9].toString(), '');
assert.equal(doc.arr.length, 10);
doc = await save(doc);
assert.equal(doc.arr.length, 10);
assert.equal(doc.arr[0], 3);
assert.strictEqual(1, doc.arr[1].x);
assert.equal(doc.arr[2], null);
assert.ok(Array.isArray(doc.arr[3]));
assert.equal(doc.arr[3][0], 5);
assert.equal(doc.arr[4], 'last');
assert.strictEqual(null, doc.arr[5]);
assert.strictEqual(null, doc.arr[6]);
assert.strictEqual(null, doc.arr[7]);
assert.strictEqual(Infinity, doc.arr[8]);
assert.equal(doc.arr[9].toString(), '');
});
it('works with sub-docs', async function() {
const D = db.model('Test', Schema({ arr: [{ name: String }] }));
const m = new D({ arr: [{ name: 'aaron' }, { name: 'moombahton ' }] });
let doc = await save(m);
assert.equal(doc.arr.length, 2);
doc.arr[0] = { name: 'vdrums' };
assert.equal(doc.arr.length, 2);
assert.equal(doc.arr[0].name, 'vdrums');
doc.arr.set(doc.arr.length, { name: 'Restrepo' });
assert.equal(doc.arr.length, 3);
assert.equal(doc.arr[2].name, 'Restrepo');
doc = await save(doc);
assert.equal(doc.arr.length, 3);
assert.equal(doc.arr[0].name, 'vdrums');
assert.equal(doc.arr[1].name, 'moombahton ');
assert.equal(doc.arr[2].name, 'Restrepo');
doc.arr.set(10, { name: 'temple of doom' });
assert.equal(doc.arr.length, 11);
assert.equal(doc.arr[10].name, 'temple of doom');
assert.equal(doc.arr[9], null);
doc = await save(doc);
assert.equal(doc.arr.length, 11);
assert.equal(doc.arr[0].name, 'vdrums');
assert.equal(doc.arr[1].name, 'moombahton ');
assert.equal(doc.arr[2].name, 'Restrepo');
assert.equal(doc.arr[3], null);
assert.equal(doc.arr[9], null);
assert.equal(doc.arr[10].name, 'temple of doom');
doc.arr.remove(doc.arr[0]);
doc.arr.set(7, { name: 7 });
assert.strictEqual('7', doc.arr[7].name);
assert.equal(doc.arr.length, 10);
doc = await save(doc);
assert.equal(doc.arr.length, 10);
assert.equal(doc.arr[0].name, 'moombahton ');
assert.equal(doc.arr[1].name, 'Restrepo');
assert.equal(doc.arr[2], null);
assert.ok(doc.arr[7]);
assert.strictEqual('7', doc.arr[7].name);
assert.equal(doc.arr[8], null);
assert.equal(doc.arr[9].name, 'temple of doom');
});
it('applies setters (gh-3032)', async function() {
const ST = db.model('Test', Schema({
arr: [{
type: String,
lowercase: true
}]
}));
const m = new ST({ arr: ['ONE', 'TWO'] });
let doc = await save(m);
assert.equal(doc.arr.length, 2);
doc.arr.set(0, 'THREE');
assert.strictEqual('three', doc.arr[0]);
assert.strictEqual('two', doc.arr[1]);
doc.arr[doc.arr.length] = 'FOUR';
assert.strictEqual('three', doc.arr[0]);
assert.strictEqual('two', doc.arr[1]);
assert.strictEqual('four', doc.arr[2]);
doc = await save(doc);
assert.equal(doc.arr.length, 3);
assert.strictEqual('three', doc.arr[0]);
assert.strictEqual('two', doc.arr[1]);
assert.strictEqual('four', doc.arr[2]);
});
});
describe('slice', function() {
it('copies schema correctly (gh-8482)', function() {
const M = db.model('Test', Schema({ arr: [Number] }));
const doc = new M({ arr: [1, 2, 3] });
const arr = doc.arr.slice(2);
arr.splice(1, 0, 5, 7, 11);
assert.deepEqual(arr, [3, 5, 7, 11]);
});
it('with unshift (gh-8482)', function() {
const M = db.model('Test', Schema({ arr: [Number] }));
const doc = new M({ arr: [1, 2, 3] });
const arr = doc.arr.slice(2);
arr.unshift(10);
assert.deepEqual(arr, [10, 3]);
});
it('with push (gh-8655)', function() {
const userSchema = new Schema({ names: [String] });
const User = mongoose.model('User', userSchema);
const user = new User({ names: ['test1', 'test2', 'test3'] });
const slicedNames = user.names.slice(1, 2);
slicedNames.push('test4');
assert.ok(slicedNames.indexOf('test2') !== -1 && slicedNames.indexOf('test4') !== -1);
});
});
describe('setting a doc array', function() {
it('should adjust path positions', async function() {
const D = db.model('Test', new Schema({
em1: [new Schema({ name: String })]
}));
let d = new D({
em1: [
{ name: 'pos0' },
{ name: 'pos1' },
{ name: 'pos2' }
]
});
await d.save();
d = await D.findById(d);
const n = d.em1.slice();
n[2].name = 'position two';
let x = [];
x[1] = n[2];
x[2] = n[1];
x = x.filter(Boolean);
d.em1 = x;
await d.save();
d = await D.findById(d);
assert.equal(d.em1[0].name, 'position two');
assert.equal(d.em1[1].name, 'pos1');
});
});
describe('paths with similar names', function() {
it('should be saved', async function() {
const D = db.model('Test', new Schema({
account: {
role: String,
roles: [String]
},
em: [new Schema({ name: String })]
}));
let d = new D({
account: { role: 'teacher', roles: ['teacher', 'admin'] },
em: [{ name: 'bob' }]
});
await d.save();
d = await D.findById(d);
d.account.role = 'president';
d.account.roles = ['president', 'janitor'];
d.em[0].name = 'memorable';
d.em = [{ name: 'frida' }];
await d.save();
d = await D.findById(d);
assert.equal(d.account.role, 'president');
assert.equal(d.account.roles.length, 2);
assert.equal(d.account.roles[0], 'president');
assert.equal(d.account.roles[1], 'janitor');
assert.equal(d.em.length, 1);
assert.equal(d.em[0].name, 'frida');
});
});
describe('of number', function() {
it('allows null and undefined', async function() {
const schema = new Schema({ x: [Number] });
const M = db.model('Test', schema);
let m;
m = new M({ x: [1, null, 3] });
await m.save();
m = new M({ x: [1, undefined, 3] });
await m.save();
m.x = [1,, 3];
await m.save();
assert.strictEqual(m.x[1], void 0);
m.x.set(1, 2);
await m.save();
assert.deepEqual(m.toObject().x, [1, 2, 3]);
});
});
describe('bug fixes', function() {
it('modifying subdoc props and manipulating the array works (gh-842)', async function() {
const schema = new Schema({ em: [new Schema({ username: String })] });
const M = db.model('Test', schema);
let m = new M({ em: [{ username: 'Arrietty' }] });
await m.save();
m = await M.findById(m);
assert.equal(m.em[0].username, 'Arrietty');
m.em[0].username = 'Shawn';
m.em.push({ username: 'Homily' });
await m.save();
m = await M.findById(m);
assert.equal(m.em.length, 2);
assert.equal(m.em[0].username, 'Shawn');
assert.equal(m.em[1].username, 'Homily');
m.em[0].username = 'Arrietty';
m.em[1].deleteOne();
await m.save();
m = await M.findById(m);
assert.equal(m.em.length, 1);
assert.equal(m.em[0].username, 'Arrietty');
});
it('toObject returns a vanilla JavaScript array (gh-9540)', function() {
const schema = new Schema({ arr: [Number] });
const M = db.model('Test', schema);
const doc = new M({ arr: [1, 2, 3] });
let arr = doc.arr.toObject();
assert.ok(Array.isArray(arr));
assert.equal(arr.constructor, Array);
assert.deepStrictEqual(arr, [1, 2, 3]);
arr = doc.arr.toObject({ depopulate: true });
assert.ok(Array.isArray(arr));
assert.equal(arr.constructor, Array);
assert.deepStrictEqual(arr, [1, 2, 3]);
});
it('pushing top level arrays and subarrays works (gh-1073)', async function() {
const schema = new Schema({ em: [new Schema({ sub: [String] })] });
const M = db.model('Test', schema);
let m = new M({ em: [{ sub: [] }] });
await m.save();
m = await M.findById(m);
m.em[m.em.length - 1].sub.push('a');
m.em.push({ sub: [] });
assert.equal(m.em.length, 2);
assert.equal(m.em[0].sub.length, 1);
await m.save();
m = await M.findById(m);
assert.equal(m.em.length, 2);
assert.equal(m.em[0].sub.length, 1);
assert.equal(m.em[0].sub[0], 'a');
});
it('finding ids by string (gh-4011)', function(done) {
const sub = new Schema({
_id: String,
other: String
});
const main = new Schema({
subs: [sub]
});
const Model = db.model('Test', main);
const doc = new Model({ subs: [{ _id: '57067021ee0870440c76f489' }] });
assert.ok(doc.subs.id('57067021ee0870440c76f489'));
assert.ok(doc.subs.id(new mongodb.ObjectId('57067021ee0870440c76f489')));
done();
});
});
describe('default type', function() {
it('casts to Mixed', function(done) {
const DefaultArraySchema = new Schema({
num1: Array,
num2: []
});
const DefaultArray = db.model('Test', DefaultArraySchema);
const arr = new DefaultArray();
assert.equal(arr.get('num1').length, 0);
assert.equal(arr.get('num2').length, 0);
let threw1 = false,
threw2 = false;
try {
arr.num1.push({ x: 1 });
arr.num1.push(9);
arr.num1.push('woah');
} catch {
threw1 = true;
}
assert.equal(threw1, false);
try {
arr.num2.push({ x: 1 });
arr.num2.push(9);
arr.num2.push('woah');
} catch {
threw2 = true;
}
assert.equal(threw2, false);
done();
});
});
describe('removing from an array atomically using MongooseArray#remove', function() {
let B;
before(function() {
const schema = new Schema({
numbers: ['number'],
numberIds: [{ _id: 'number', name: 'string' }],
stringIds: [{ _id: 'string', name: 'string' }],
bufferIds: [{ _id: 'buffer', name: 'string' }],
oidIds: [{ name: 'string' }]
});
B = db.model('BlogPost', schema);
});
it('works', async function() {
const post = new B();
post.numbers.push(1, 2, 3);
await post.save();
let doc = await B.findById(post._id);
doc.numbers.remove('1');
await doc.save();
doc = await B.findById(post.get('_id'));
assert.equal(doc.numbers.length, 2);
doc.numbers.remove('2', '3');
await doc.save();
doc = await B.findById(post._id);
assert.equal(doc.numbers.length, 0);
});
describe('with subdocs', function() {
function docs(arr) {
return arr.map(function(val) {
return { _id: val };
});
}
it('supports passing strings', async function() {
let post = new B({ stringIds: docs('a b c d'.split(' ')) });
await post.save();
post = await B.findById(post);
post.stringIds.remove('b');
await post.save();
post = await B.findById(post);
assert.equal(post.stringIds.length, 3);
assert.ok(!post.stringIds.id('b'));
});
it('supports passing numbers', async function() {
let post = new B({ numberIds: docs([1, 2, 3, 4]) });
await post.save();
post = await B.findById(post);
post.numberIds.remove(2, 4);
await post.save();
post = await B.findById(post);
assert.equal(post.numberIds.length, 2);
assert.ok(!post.numberIds.id(2));
assert.ok(!post.numberIds.id(4));
});
it('supports passing objectids', async function() {
const OID = mongoose.Types.ObjectId;
const a = new OID();
const b = new OID();
const c = new OID();
let post = new B({ oidIds: docs([a, b, c]) });
await post.save();
post = await B.findById(post);
post.oidIds.remove(a, c);
await post.save();
post = await B.findById(post);
assert.equal(post.oidIds.length, 1);
assert.ok(!post.oidIds.id(a));
assert.ok(!post.oidIds.id(c));
});
it('supports passing buffers', async function() {
let post = new B({ bufferIds: docs(['a', 'b', 'c', 'd']) });
await post.save();
post = await B.findById(post);
post.bufferIds.remove(Buffer.from('a'));
await post.save();
post = await B.findById(post);
assert.equal(post.bufferIds.length, 3);
assert.ok(!post.bufferIds.id(Buffer.from('a')));
});
});
});
describe('built-in array methods that modify element structure return vanilla arrays (gh-8356)', function() {
beforeEach(function() {
mongoose.deleteModel(/Test/);
});
it('filter', function() {
const Model = mongoose.model('Test', Schema({ arr: [String] }));
const doc = new Model({ arr: ['foo', 'bar', 'baz'] });
const arr = doc.arr.filter(str => str.startsWith('b'));
assert.deepEqual(arr, ['bar', 'baz']);
assert.ok(!arr.isMongooseArray);
});
it('flat', function() {
if (Array.prototype.flat == null) {
return this.skip();
}
const Model = mongoose.model('Test', Schema({ arr: [[String]] }));
const doc = new Model({ arr: [['foo']] });
const arr = doc.arr.flat();
assert.deepEqual(arr, ['foo']);
assert.ok(!arr.isMongooseArray);
});
it('flatMap', function() {
if (Array.prototype.flatMap == null) {
return this.skip();
}
const Model = mongoose.model('Test', Schema({ arr: [Number] }));
const doc = new Model({ arr: [1, 3, 5, 7] });
const arr = doc.arr.flatMap(v => [v, v + 1]);
assert.deepEqual(arr, [1, 2, 3, 4, 5, 6, 7, 8]);
assert.ok(!arr.isMongooseArray);
});
it('map', function() {
const Model = mongoose.model('Test', Schema({ arr: [Number] }));
const doc = new Model({ arr: [2, 4, 6, 8] });
const arr = doc.arr.map(v => v / 2);
assert.deepEqual(arr, [1, 2, 3, 4]);
assert.ok(!arr.isMongooseArray);
});
it('slice', function() {
const Model = mongoose.model('Test', Schema({ arr: [Number] }));
const doc = new Model({ arr: [2, 4, 6, 8] });
const arr = doc.arr.slice(1, 3);
assert.deepEqual(arr, [4, 6]);
assert.ok(!arr.isMongooseArray);
});
});
it('does not mutate passed-in array (gh-10766)', function() {
const Test = db.model('Test', new Schema({ arr: [String] }));
const arr = [42];
const doc = new Test({ arr });
assert.strictEqual(doc.arr[0], '42');
assert.strictEqual(arr[0], 42);
});
it('test "castNonArrays" property option', function() {
const Model = db.model('Test', new Schema({ x1: { castNonArrays: false, type: [String] }, x2: { castNonArrays: true, type: [String] }, x3: { type: [String] } }));
const string = 'hello';
let doc = new Model({ x1: string });
const validateErrors = doc.validateSync().errors;
assert.ok(validateErrors);
assert.equal(validateErrors['x1'].name, 'CastError');
doc = new Model({ x2: string });
assert.ifError(doc.validateSync());
doc.x2.push('foo');
assert.ifError(doc.validateSync());
assert.deepEqual(doc.x2.toObject(), ['hello', 'foo']);
doc = new Model({ x3: string });
assert.ifError(doc.validateSync());
doc.x3.push('foo');
assert.ifError(doc.validateSync());
assert.deepEqual(doc.x3.toObject(), ['hello', 'foo']);
});
it('`castNonArrays` on specific paths takes precedence over global option', function() {
const m = new mongoose.Mongoose();
m.Schema.Types.Array.options.castNonArrays = false;
const userSchema = new Schema({ friendsNames: { type: [String], castNonArrays: true } });
const User = m.model('User', userSchema);
const user = new User({ friendsNames: 'Sam' });
assert.ifError(user.validateSync());
m.Schema.Types.Array.options.castNonArrays = true;
});
it('supports setting nested arrays directly (gh-13372)', function() {
const Test = db.model('Test', new Schema({ intArr: [[Number]] }));
const intArr = [[1, 2], [3, 4]];
const doc = Test.hydrate({ intArr });
doc.intArr[0][0] = 2;
doc.intArr[1][1] = 5;
assert.deepStrictEqual(doc.getChanges(), {
$set: {
'intArr.0.0': 2,
'intArr.1.1': 5
}
});
});
it('calls array setters (gh-11380)', function() {
let called = 0;
const Test = db.model('Test', new Schema({
intArr: [{
type: Number,
set: v => {
++called;
return Math.floor(v);
}
}]
}));
assert.equal(called, 0);
const doc = new Test({ intArr: [3.14] });
assert.deepStrictEqual(doc.intArr, [3]);
assert.equal(called, 1);
doc.intArr.push(2.718);
assert.deepStrictEqual(doc.intArr, [3, 2]);
assert.equal(called, 2);
});
it('does not mutate SchemaArray setters order when casting an array-valued query filter (gh-16372)', function() {
const schema = new Schema({ nums: { type: [Number], set: v => v } });
schema.path('nums').set(v => v);
const originalOrder = schema.path('nums').setters.slice();
const M = db.model('Test', schema);
M.find({ nums: [1, 2, 3] }).cast(M);
assert.deepStrictEqual(schema.path('nums').setters, originalOrder);
});
});