Skip to content

Commit e7ab63f

Browse files
fix(documentArray): fix change tracking for documentArrays in nested maps (#15983)
* test(map): test document arrays in nested maps * fix(documentArray): strip path from options in subdoc $init re #15350 #15678 #15970 #15682
1 parent a9ec84d commit e7ab63f

File tree

2 files changed

+77
-0
lines changed

2 files changed

+77
-0
lines changed

lib/schema/documentArray.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,12 @@ SchemaDocumentArray.prototype.cast = function(value, doc, init, prev, options) {
464464
}
465465

466466
subdoc = new Constructor(null, value, initDocumentOptions, selected, i);
467+
// Don't pass `path` to $init - it's only for this DocumentArray itself, not its element fields.
468+
// Element subdocuments use relative paths internally for change tracking.
469+
if (options.path != null) {
470+
options = { ...options };
471+
delete options.path;
472+
}
467473
rawArray[i] = subdoc.$init(rawArray[i], options);
468474
} else {
469475
if (typeof prev?.id === 'function') {

test/types.map.test.js

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1914,4 +1914,75 @@ describe('Map', function() {
19141914
return { Company, company };
19151915
}
19161916
});
1917+
1918+
describe('nested Maps inside DocumentArray elements loaded via init() (gh-15678)', function() {
1919+
it('map.set() on a Map inside a DocumentArray element produces correct paths', function() {
1920+
// Arrange
1921+
const { company } = createTestContext();
1922+
1923+
// Act
1924+
company.events.get('techConf')[0].ratings.set('content', 5);
1925+
1926+
// Assert
1927+
assert.deepStrictEqual(company.getChanges(), {
1928+
$set: { 'events.techConf.0.ratings.content': 5 }
1929+
});
1930+
});
1931+
1932+
it('map.set() with subdocument value inside a DocumentArray element produces correct paths', function() {
1933+
// Arrange
1934+
const { company } = createTestContext();
1935+
1936+
// Act
1937+
company.events.get('techConf')[0].speakers.set('panelist', { name: 'Bob' });
1938+
1939+
// Assert
1940+
assert.deepStrictEqual(company.getChanges(), {
1941+
$set: { 'events.techConf.0.speakers.panelist': { name: 'Bob' } }
1942+
});
1943+
});
1944+
1945+
it('validation reports correct paths for nested Maps inside DocumentArray elements', async function() {
1946+
// Arrange
1947+
const { company } = createTestContext({ speakerNameMinLength: 2 });
1948+
1949+
// Act
1950+
company.events.get('techConf')[0].speakers.set('guest', { name: 'X' });
1951+
const error = await company.validate().then(() => null, err => err);
1952+
1953+
// Assert
1954+
assert.ok(error);
1955+
assert.ok(error.errors['events.techConf.0.speakers.guest.name']);
1956+
});
1957+
1958+
function createTestContext({ speakerNameMinLength } = {}) {
1959+
const speakerSchema = new Schema({
1960+
name: { type: String, minlength: speakerNameMinLength }
1961+
}, { _id: false });
1962+
1963+
const sessionSchema = new Schema({
1964+
title: String,
1965+
speakers: { type: Map, of: speakerSchema },
1966+
ratings: { type: Map, of: Number }
1967+
}, { _id: false });
1968+
1969+
const companySchema = new Schema({
1970+
events: { type: Map, of: [sessionSchema] }
1971+
});
1972+
1973+
const Company = db.model('Company', companySchema);
1974+
1975+
const company = new Company();
1976+
company.init({
1977+
_id: new mongoose.Types.ObjectId(),
1978+
events: {
1979+
techConf: [
1980+
{ title: 'Keynote', speakers: { host: { name: 'Alice' } }, ratings: { overall: 4 } }
1981+
]
1982+
}
1983+
});
1984+
1985+
return { Company, company };
1986+
}
1987+
});
19171988
});

0 commit comments

Comments
 (0)