mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
345 lines
10 KiB
JavaScript
345 lines
10 KiB
JavaScript
Tinytest.add("diff-sequence - diff changes ordering", function (test) {
|
|
const makeDocs = function (ids) {
|
|
return ids.map(function (id) {
|
|
return { _id: id };
|
|
});
|
|
};
|
|
const testMutation = function (a, b) {
|
|
const aa = makeDocs(a);
|
|
const bb = makeDocs(b);
|
|
const aaCopy = EJSON.clone(aa);
|
|
DiffSequence.diffQueryOrderedChanges(aa, bb, {
|
|
addedBefore: function (id, doc, before) {
|
|
if (before === null) {
|
|
aaCopy.push(Object.assign({ _id: id }, doc));
|
|
return;
|
|
}
|
|
for (let i = 0; i < aaCopy.length; i++) {
|
|
if (aaCopy[i]._id === before) {
|
|
aaCopy.splice(i, 0, Object.assign({ _id: id }, doc));
|
|
return;
|
|
}
|
|
}
|
|
},
|
|
movedBefore: function (id, before) {
|
|
let found;
|
|
for (let i = 0; i < aaCopy.length; i++) {
|
|
if (aaCopy[i]._id === id) {
|
|
found = aaCopy[i];
|
|
aaCopy.splice(i, 1);
|
|
}
|
|
}
|
|
if (before === null) {
|
|
aaCopy.push(Object.assign({ _id: id }, found));
|
|
return;
|
|
}
|
|
for (let i = 0; i < aaCopy.length; i++) {
|
|
if (aaCopy[i]._id === before) {
|
|
aaCopy.splice(i, 0, Object.assign({ _id: id }, found));
|
|
return;
|
|
}
|
|
}
|
|
},
|
|
removed: function (id) {
|
|
for (let i = 0; i < aaCopy.length; i++) {
|
|
if (aaCopy[i]._id === id) {
|
|
aaCopy.splice(i, 1);
|
|
}
|
|
}
|
|
},
|
|
});
|
|
test.equal(aaCopy, bb);
|
|
};
|
|
|
|
const testBothWays = function (a, b) {
|
|
testMutation(a, b);
|
|
testMutation(b, a);
|
|
};
|
|
|
|
testBothWays(["a", "b", "c"], ["c", "b", "a"]);
|
|
testBothWays(["a", "b", "c"], []);
|
|
testBothWays(["a", "b", "c"], ["e", "f"]);
|
|
testBothWays(["a", "b", "c", "d"], ["c", "b", "a"]);
|
|
testBothWays(
|
|
["A", "B", "C", "D", "E", "F", "G", "H", "I"],
|
|
["A", "B", "F", "G", "C", "D", "I", "L", "M", "N", "H"],
|
|
);
|
|
testBothWays(
|
|
["A", "B", "C", "D", "E", "F", "G", "H", "I"],
|
|
["A", "B", "C", "D", "F", "G", "H", "E", "I"],
|
|
);
|
|
});
|
|
|
|
Tinytest.add("diff-sequence - diff", function (test) {
|
|
// test correctness
|
|
|
|
const diffTest = function (origLen, newOldIdx) {
|
|
const oldResults = Array.from({ length: origLen });
|
|
for (let i = 1; i <= origLen; i++) oldResults[i - 1] = { _id: i };
|
|
|
|
const newResults = newOldIdx.map(function (n) {
|
|
const doc = { _id: Math.abs(n) };
|
|
if (n < 0) doc.changed = true;
|
|
return doc;
|
|
});
|
|
const find = function (arr, id) {
|
|
for (let i = 0; i < arr.length; i++) {
|
|
if (EJSON.equals(arr[i]._id, id)) return i;
|
|
}
|
|
return -1;
|
|
};
|
|
|
|
const results = [...oldResults];
|
|
const observer = {
|
|
addedBefore: function (id, fields, before) {
|
|
let before_idx;
|
|
if (before === null) before_idx = results.length;
|
|
else before_idx = find(results, before);
|
|
const doc = Object.assign({ _id: id }, fields);
|
|
test.isFalse(before_idx < 0 || before_idx > results.length);
|
|
results.splice(before_idx, 0, doc);
|
|
},
|
|
removed: function (id) {
|
|
const at_idx = find(results, id);
|
|
test.isFalse(at_idx < 0 || at_idx >= results.length);
|
|
results.splice(at_idx, 1);
|
|
},
|
|
changed: function (id, fields) {
|
|
const at_idx = find(results, id);
|
|
const oldDoc = results[at_idx];
|
|
const doc = EJSON.clone(oldDoc);
|
|
DiffSequence.applyChanges(doc, fields);
|
|
test.isFalse(at_idx < 0 || at_idx >= results.length);
|
|
test.equal(doc._id, oldDoc._id);
|
|
results[at_idx] = doc;
|
|
},
|
|
movedBefore: function (id, before) {
|
|
const old_idx = find(results, id);
|
|
let new_idx;
|
|
if (before === null) new_idx = results.length;
|
|
else new_idx = find(results, before);
|
|
if (new_idx > old_idx) new_idx--;
|
|
test.isFalse(old_idx < 0 || old_idx >= results.length);
|
|
test.isFalse(new_idx < 0 || new_idx >= results.length);
|
|
results.splice(new_idx, 0, results.splice(old_idx, 1)[0]);
|
|
},
|
|
};
|
|
|
|
DiffSequence.diffQueryOrderedChanges(oldResults, newResults, observer);
|
|
test.equal(results, newResults);
|
|
};
|
|
|
|
// edge cases and cases run into during debugging
|
|
diffTest(5, [5, 1, 2, 3, 4]);
|
|
diffTest(0, [1, 2, 3, 4]);
|
|
diffTest(4, []);
|
|
diffTest(7, [4, 5, 6, 7, 1, 2, 3]);
|
|
diffTest(7, [5, 6, 7, 1, 2, 3, 4]);
|
|
diffTest(10, [7, 4, 11, 6, 12, 1, 5]);
|
|
diffTest(3, [3, 2, 1]);
|
|
diffTest(10, [2, 7, 4, 6, 11, 3, 8, 9]);
|
|
diffTest(0, []);
|
|
diffTest(1, []);
|
|
diffTest(0, [1]);
|
|
diffTest(1, [1]);
|
|
diffTest(5, [1, 2, 3, 4, 5]);
|
|
|
|
// interaction between "changed" and other ops
|
|
diffTest(5, [-5, -1, 2, -3, 4]);
|
|
diffTest(7, [-4, -5, 6, 7, -1, 2, 3]);
|
|
diffTest(7, [5, 6, -7, 1, 2, -3, 4]);
|
|
diffTest(10, [7, -4, 11, 6, 12, -1, 5]);
|
|
diffTest(3, [-3, -2, -1]);
|
|
diffTest(10, [-2, 7, 4, 6, 11, -3, -8, 9]);
|
|
});
|
|
|
|
Tinytest.add("diff-sequence - diffObjects partitions keys", (test) => {
|
|
const left = { a: 1, b: 2, c: 3 };
|
|
const right = { b: 2, c: 4, d: 5 };
|
|
|
|
const leftOnly = [];
|
|
const rightOnly = [];
|
|
const both = [];
|
|
|
|
DiffSequence.diffObjects(left, right, {
|
|
leftOnly: (key, value) => leftOnly.push([key, value]),
|
|
rightOnly: (key, value) => rightOnly.push([key, value]),
|
|
both: (key, leftValue, rightValue) => both.push([key, leftValue, rightValue]),
|
|
});
|
|
|
|
test.equal(leftOnly, [["a", 1]]);
|
|
test.equal(rightOnly, [["d", 5]]);
|
|
// Sort `both` to make the test independent of Object.keys iteration order.
|
|
both.sort((x, y) => x[0].localeCompare(y[0]));
|
|
test.equal(both, [
|
|
["b", 2, 2],
|
|
["c", 3, 4],
|
|
]);
|
|
});
|
|
|
|
Tinytest.add("diff-sequence - diffObjects omits missing callbacks", (test) => {
|
|
const left = { a: 1, b: 2 };
|
|
const right = { b: 3, c: 4 };
|
|
|
|
let bothCount = 0;
|
|
// Only `both` is provided — leftOnly and rightOnly are absent and must not throw.
|
|
DiffSequence.diffObjects(left, right, {
|
|
both: () => {
|
|
bothCount++;
|
|
},
|
|
});
|
|
test.equal(bothCount, 1);
|
|
test.ok(); // reaching here means no exception was thrown
|
|
});
|
|
|
|
Tinytest.add("diff-sequence - diffMaps partitions keys", (test) => {
|
|
const left = new Map([
|
|
["a", 1],
|
|
["b", 2],
|
|
["c", 3],
|
|
]);
|
|
const right = new Map([
|
|
["b", 2],
|
|
["c", 4],
|
|
["d", 5],
|
|
]);
|
|
|
|
const leftOnly = [];
|
|
const rightOnly = [];
|
|
const both = [];
|
|
|
|
DiffSequence.diffMaps(left, right, {
|
|
leftOnly: (key, value) => leftOnly.push([key, value]),
|
|
rightOnly: (key, value) => rightOnly.push([key, value]),
|
|
both: (key, leftValue, rightValue) => both.push([key, leftValue, rightValue]),
|
|
});
|
|
|
|
test.equal(leftOnly, [["a", 1]]);
|
|
test.equal(rightOnly, [["d", 5]]);
|
|
both.sort((x, y) => x[0].localeCompare(y[0]));
|
|
test.equal(both, [
|
|
["b", 2, 2],
|
|
["c", 3, 4],
|
|
]);
|
|
});
|
|
|
|
Tinytest.add("diff-sequence - makeChangedFields detects adds, removes, changes", (test) => {
|
|
const oldDoc = { a: 1, b: 2, c: 3 };
|
|
const newDoc = { a: 1, b: 99, d: 4 };
|
|
|
|
const changed = DiffSequence.makeChangedFields(newDoc, oldDoc);
|
|
|
|
// 'a' unchanged, 'b' changed, 'c' removed (undefined), 'd' added.
|
|
test.equal(changed, { b: 99, c: undefined, d: 4 });
|
|
});
|
|
|
|
Tinytest.add("diff-sequence - makeChangedFields uses EJSON.equals for deep compare", (test) => {
|
|
const same = DiffSequence.makeChangedFields(
|
|
{ a: [1, 2, 3], b: { nested: true } },
|
|
{ a: [1, 2, 3], b: { nested: true } },
|
|
);
|
|
test.equal(same, {}, "deeply equal values should not produce a change");
|
|
|
|
const changed = DiffSequence.makeChangedFields(
|
|
{ a: [1, 2, 4], b: { nested: true } },
|
|
{ a: [1, 2, 3], b: { nested: true } },
|
|
);
|
|
test.equal(changed, { a: [1, 2, 4] });
|
|
});
|
|
|
|
Tinytest.add("diff-sequence - applyChanges adds, replaces, removes fields", (test) => {
|
|
const doc = { a: 1, b: 2 };
|
|
DiffSequence.applyChanges(doc, { a: 99, c: 3, b: undefined });
|
|
test.equal(doc, { a: 99, c: 3 });
|
|
});
|
|
|
|
Tinytest.add("diff-sequence - diffQueryUnorderedChanges detects added/removed/changed", (test) => {
|
|
const oldResults = new IdMap();
|
|
oldResults.set("x", { _id: "x", v: 1 });
|
|
oldResults.set("y", { _id: "y", v: 2 });
|
|
|
|
const newResults = new IdMap();
|
|
newResults.set("y", { _id: "y", v: 99 });
|
|
newResults.set("z", { _id: "z", v: 3 });
|
|
|
|
const added = [];
|
|
const removed = [];
|
|
const changed = [];
|
|
|
|
DiffSequence.diffQueryUnorderedChanges(oldResults, newResults, {
|
|
added: (id, fields) => added.push([id, fields]),
|
|
removed: (id) => removed.push(id),
|
|
changed: (id, fields) => changed.push([id, fields]),
|
|
});
|
|
|
|
test.equal(added, [["z", { v: 3 }]]);
|
|
test.equal(removed, ["x"]);
|
|
test.equal(changed, [["y", { v: 99 }]]);
|
|
});
|
|
|
|
Tinytest.add(
|
|
"diff-sequence - diffQueryUnorderedChanges throws with a movedBefore observer",
|
|
(test) => {
|
|
const oldResults = new IdMap();
|
|
const newResults = new IdMap();
|
|
test.throws(
|
|
() =>
|
|
DiffSequence.diffQueryUnorderedChanges(oldResults, newResults, {
|
|
movedBefore: () => {},
|
|
}),
|
|
/movedBefore/,
|
|
);
|
|
},
|
|
);
|
|
|
|
Tinytest.add("diff-sequence - diffQueryChanges dispatches based on ordered flag", (test) => {
|
|
// Unordered path.
|
|
const oldUnordered = new IdMap();
|
|
oldUnordered.set("a", { _id: "a", v: 1 });
|
|
const newUnordered = new IdMap();
|
|
newUnordered.set("a", { _id: "a", v: 2 });
|
|
|
|
const unorderedChanged = [];
|
|
DiffSequence.diffQueryChanges(false, oldUnordered, newUnordered, {
|
|
changed: (id, fields) => unorderedChanged.push([id, fields]),
|
|
});
|
|
test.equal(unorderedChanged, [["a", { v: 2 }]]);
|
|
|
|
// Ordered path — uses addedBefore/movedBefore.
|
|
const oldOrdered = [{ _id: "a", v: 1 }];
|
|
const newOrdered = [
|
|
{ _id: "a", v: 1 },
|
|
{ _id: "b", v: 2 },
|
|
];
|
|
|
|
const orderedAddedBefore = [];
|
|
DiffSequence.diffQueryChanges(true, oldOrdered, newOrdered, {
|
|
addedBefore: (id, fields, before) => orderedAddedBefore.push([id, fields, before]),
|
|
});
|
|
test.equal(orderedAddedBefore, [["b", { v: 2 }, null]]);
|
|
});
|
|
|
|
Tinytest.add("diff-sequence - projectionFn is applied before change detection", (test) => {
|
|
const oldResults = new IdMap();
|
|
oldResults.set("a", { _id: "a", visible: 1, hidden: 10 });
|
|
|
|
const newResults = new IdMap();
|
|
// Only the `hidden` field changed.
|
|
newResults.set("a", { _id: "a", visible: 1, hidden: 999 });
|
|
|
|
const changed = [];
|
|
DiffSequence.diffQueryUnorderedChanges(
|
|
oldResults,
|
|
newResults,
|
|
{
|
|
changed: (id, fields) => changed.push([id, fields]),
|
|
},
|
|
{
|
|
projectionFn: (doc) => ({ _id: doc._id, visible: doc.visible }),
|
|
},
|
|
);
|
|
|
|
// The projection drops `hidden`, so no change is reported.
|
|
test.equal(changed, []);
|
|
});
|