Skip to content

Commit 017b16e

Browse files
authored
Merge pull request #14053 from Automattic/vkarpov15/gh-14022
fix(document): avoid unmarking modified on nested path if no initial value stored and already modified
2 parents 4c66fcb + 4bd5927 commit 017b16e

File tree

2 files changed

+76
-2
lines changed

2 files changed

+76
-2
lines changed

lib/document.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1204,6 +1204,7 @@ Document.prototype.$set = function $set(path, val, type, options) {
12041204
this.invalidate(path, new MongooseError.CastError('Object', val, path));
12051205
return this;
12061206
}
1207+
const wasModified = this.$isModified(path);
12071208
const hasInitialVal = this.$__.savedState != null && this.$__.savedState.hasOwnProperty(path);
12081209
if (this.$__.savedState != null && !this.$isNew && !this.$__.savedState.hasOwnProperty(path)) {
12091210
const initialVal = this.$__getValue(path);
@@ -1226,9 +1227,11 @@ Document.prototype.$set = function $set(path, val, type, options) {
12261227

12271228
this.$__setValue(path, {});
12281229
for (const key of keys) {
1229-
this.$set(path + '.' + key, val[key], constructing, options);
1230+
this.$set(path + '.' + key, val[key], constructing, { ...options, _skipMarkModified: true });
12301231
}
1231-
if (priorVal != null && utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) {
1232+
if (priorVal != null &&
1233+
(!wasModified || hasInitialVal) &&
1234+
utils.deepEqual(hasInitialVal ? this.$__.savedState[path] : priorVal, val)) {
12321235
this.unmarkModified(path);
12331236
} else {
12341237
this.markModified(path);

test/document.test.js

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3731,6 +3731,11 @@ describe('document', function() {
37313731

37323732
assert.deepEqual(
37333733
kitty.modifiedPaths(),
3734+
['surnames']
3735+
);
3736+
3737+
assert.deepEqual(
3738+
kitty.modifiedPaths({ includeChildren: true }),
37343739
['surnames', 'surnames.docarray']
37353740
);
37363741
});
@@ -9239,6 +9244,49 @@ describe('document', function() {
92399244
assert.ok(foo.isModified('subdoc.bar'));
92409245
});
92419246

9247+
it('does not unmark modified if there is no initial value (gh-9396)', async function() {
9248+
const IClientSchema = new Schema({
9249+
jwt: {
9250+
token_crypt: { type: String, template: false, maxSize: 8 * 1024 },
9251+
token_salt: { type: String, template: false }
9252+
}
9253+
});
9254+
9255+
const encrypt = function(doc, path, value) {
9256+
doc.set(path + '_crypt', value + '_crypt');
9257+
doc.set(path + '_salt', value + '_salt');
9258+
};
9259+
9260+
const decrypt = function(doc, path) {
9261+
return doc.get(path + '_crypt').replace('_crypt', '');
9262+
};
9263+
9264+
IClientSchema.virtual('jwt.token')
9265+
.get(function() {
9266+
return decrypt(this, 'jwt.token');
9267+
})
9268+
.set(function(value) {
9269+
encrypt(this, 'jwt.token', value);
9270+
});
9271+
9272+
9273+
const iclient = db.model('Test', IClientSchema);
9274+
const test = new iclient({
9275+
jwt: {
9276+
token: 'firstToken'
9277+
}
9278+
});
9279+
9280+
await test.save();
9281+
const entry = await iclient.findById(test._id).orFail();
9282+
entry.set('jwt.token', 'secondToken');
9283+
entry.set(entry.toJSON());
9284+
await entry.save();
9285+
9286+
const { jwt } = await iclient.findById(test._id).orFail();
9287+
assert.strictEqual(jwt.token, 'secondToken');
9288+
});
9289+
92429290
it('correctly tracks saved state for deeply nested objects (gh-10773) (gh-9396)', async function() {
92439291
const PaymentSchema = Schema({ status: String }, { _id: false });
92449292
const OrderSchema = new Schema({
@@ -12312,6 +12360,29 @@ describe('document', function() {
1231212360
const nestedProjectionDoc = await User.findOne({}, { name: 1, 'sub.propertyA': 1, 'sub.propertyB': 1 });
1231312361
assert.strictEqual(nestedProjectionDoc.sub.propertyA, 'A');
1231412362
});
12363+
12364+
it('avoids adding nested paths to markModified() output if adding a new field (gh-14024)', async function() {
12365+
const eventSchema = new Schema({
12366+
name: { type: String },
12367+
__stateBeforeSuspension: {
12368+
field1: { type: String },
12369+
field2: { type: String },
12370+
jsonField: {
12371+
name: { type: String },
12372+
name1: { type: String }
12373+
}
12374+
}
12375+
});
12376+
const Event = db.model('Event', eventSchema);
12377+
const eventObj = new Event({ name: 'event object', __stateBeforeSuspension: { field1: 'test', jsonField: { name: 'test3' } } });
12378+
await eventObj.save();
12379+
const newObject = { field1: 'test', jsonField: { name: 'test3', name1: 'test4' } };
12380+
eventObj.set('__stateBeforeSuspension', newObject);
12381+
assert.deepEqual(
12382+
eventObj.modifiedPaths(),
12383+
['__stateBeforeSuspension', '__stateBeforeSuspension.jsonField']
12384+
);
12385+
});
1231512386
});
1231612387

1231712388
describe('Check if instance function that is supplied in schema option is availabe', function() {

0 commit comments

Comments
 (0)