overleaf/services/track-changes/test/unit/js/DiffGenerator/DiffGeneratorTests.js

402 lines
13 KiB
JavaScript

/* eslint-disable
no-return-assign,
no-unused-vars,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
const sinon = require('sinon');
const chai = require('chai');
const should = chai.should();
const { expect } = chai;
const modulePath = "../../../../app/js/DiffGenerator.js";
const SandboxedModule = require('sandboxed-module');
describe("DiffGenerator", function() {
beforeEach(function() {
this.DiffGenerator = SandboxedModule.require(modulePath, { requires: {
"logger-sharelatex": { warn: sinon.stub() }
}
});
this.ts = Date.now();
this.user_id = "mock-user-id";
this.user_id_2 = "mock-user-id-2";
return this.meta = {
start_ts: this.ts, end_ts: this.ts, user_id: this.user_id
};});
describe("rewindOp", function() {
describe("rewinding an insert", function() { return it("should undo the insert", function() {
const content = "hello world";
const rewoundContent = this.DiffGenerator.rewindOp(content, { p: 6, i: "wo" });
return rewoundContent.should.equal("hello rld");
}); }
);
describe("rewinding a delete", function() { return it("should undo the delete", function() {
const content = "hello rld";
const rewoundContent = this.DiffGenerator.rewindOp(content, { p: 6, d: "wo" });
return rewoundContent.should.equal("hello world");
}); }
);
describe("with an inconsistent update", function() { return it("should throw an error", function() {
const content = "hello world";
return expect( () => {
return this.DiffGenerator.rewindOp(content, { p: 6, i: "foo" });
}).to.throw(this.DiffGenerator.ConsistencyError);
}); }
);
return describe("with an update which is beyond the length of the content", function() { return it("should undo the insert as if it were at the end of the content", function() {
const content = "foobar";
const rewoundContent = this.DiffGenerator.rewindOp(content, { p: 4, i: "bar" });
return rewoundContent.should.equal("foo");
}); }
);
});
describe("rewindUpdate", function() { return it("should rewind ops in reverse", function() {
const content = "aaabbbccc";
const update =
{op: [{ p: 3, i: "bbb" }, { p: 6, i: "ccc" }]};
const rewoundContent = this.DiffGenerator.rewindUpdate(content, update);
return rewoundContent.should.equal("aaa");
}); }
);
describe("rewindUpdates", function() { return it("should rewind updates in reverse", function() {
const content = "aaabbbccc";
const updates = [
{ op: [{ p: 3, i: "bbb" }] },
{ op: [{ p: 6, i: "ccc" }] }
];
const rewoundContent = this.DiffGenerator.rewindUpdates(content, updates);
return rewoundContent.should.equal("aaa");
}); }
);
describe("buildDiff", function() {
beforeEach(function() {
this.diff = [ {u: "mock-diff"} ];
this.content = "Hello world";
this.updates = [
{ i: "mock-update-1" },
{ i: "mock-update-2" },
{ i: "mock-update-3" }
];
this.DiffGenerator.applyUpdateToDiff = sinon.stub().returns(this.diff);
this.DiffGenerator.compressDiff = sinon.stub().returns(this.diff);
return this.result = this.DiffGenerator.buildDiff(this.content, this.updates);
});
it("should return the diff", function() {
return this.result.should.deep.equal(this.diff);
});
it("should build the content into an initial diff", function() {
return this.DiffGenerator.applyUpdateToDiff
.calledWith([{
u: this.content
}], this.updates[0])
.should.equal(true);
});
it("should apply each update", function() {
return Array.from(this.updates).map((update) =>
this.DiffGenerator.applyUpdateToDiff
.calledWith(sinon.match.any, update)
.should.equal(true));
});
return it("should compress the diff", function() {
return this.DiffGenerator.compressDiff
.calledWith(this.diff)
.should.equal(true);
});
});
describe("compressDiff", function() {
describe("with adjacent inserts with the same user_id", function() { return it("should create one update with combined meta data and min/max timestamps", function() {
const diff = this.DiffGenerator.compressDiff([
{ i: "foo", meta: { start_ts: 10, end_ts: 20, user: { id: this.user_id } }},
{ i: "bar", meta: { start_ts: 5, end_ts: 15, user: { id: this.user_id } }}
]);
return expect(diff).to.deep.equal([
{ i: "foobar", meta: { start_ts: 5, end_ts: 20, user: { id: this.user_id } }}
]);
}); }
);
describe("with adjacent inserts with different user_ids", function() { return it("should leave the inserts unchanged", function() {
const input = [
{ i: "foo", meta: { start_ts: 10, end_ts: 20, user: { id: this.user_id } }},
{ i: "bar", meta: { start_ts: 5, end_ts: 15, user: { id: this.user_id_2 } }}
];
const output = this.DiffGenerator.compressDiff(input);
return expect(output).to.deep.equal(input);
}); }
);
describe("with adjacent deletes with the same user_id", function() { return it("should create one update with combined meta data and min/max timestamps", function() {
const diff = this.DiffGenerator.compressDiff([
{ d: "foo", meta: { start_ts: 10, end_ts: 20, user: { id: this.user_id } }},
{ d: "bar", meta: { start_ts: 5, end_ts: 15, user: { id: this.user_id } }}
]);
return expect(diff).to.deep.equal([
{ d: "foobar", meta: { start_ts: 5, end_ts: 20, user: { id: this.user_id } }}
]);
}); }
);
return describe("with adjacent deletes with different user_ids", function() { return it("should leave the deletes unchanged", function() {
const input = [
{ d: "foo", meta: { start_ts: 10, end_ts: 20, user: { id: this.user_id } }},
{ d: "bar", meta: { start_ts: 5, end_ts: 15, user: { id: this.user_id_2 } }}
];
const output = this.DiffGenerator.compressDiff(input);
return expect(output).to.deep.equal(input);
}); }
);
});
return describe("applyUpdateToDiff", function() {
describe("an insert", function() {
it("should insert into the middle of (u)nchanged text", function() {
const diff = this.DiffGenerator.applyUpdateToDiff(
[ { u: "foobar" } ],
{ op: [{ p: 3, i: "baz" }], meta: this.meta }
);
return expect(diff).to.deep.equal([
{ u: "foo" },
{ i: "baz", meta: this.meta },
{ u: "bar" }
]);
});
it("should insert into the start of (u)changed text", function() {
const diff = this.DiffGenerator.applyUpdateToDiff(
[ { u: "foobar" } ],
{ op: [{ p: 0, i: "baz" }], meta: this.meta }
);
return expect(diff).to.deep.equal([
{ i: "baz", meta: this.meta },
{ u: "foobar" }
]);
});
it("should insert into the end of (u)changed text", function() {
const diff = this.DiffGenerator.applyUpdateToDiff(
[ { u: "foobar" } ],
{ op: [{ p: 6, i: "baz" }], meta: this.meta }
);
return expect(diff).to.deep.equal([
{ u: "foobar" },
{ i: "baz", meta: this.meta }
]);
});
it("should insert into the middle of (i)inserted text", function() {
const diff = this.DiffGenerator.applyUpdateToDiff(
[ { i: "foobar", meta: this.meta } ],
{ op: [{ p: 3, i: "baz" }], meta: this.meta }
);
return expect(diff).to.deep.equal([
{ i: "foo", meta: this.meta },
{ i: "baz", meta: this.meta },
{ i: "bar", meta: this.meta }
]);
});
return it("should not count deletes in the running length total", function() {
const diff = this.DiffGenerator.applyUpdateToDiff(
[
{ d: "deleted", meta: this.meta },
{ u: "foobar" }
],
{ op: [{ p: 3, i: "baz" }], meta: this.meta }
);
return expect(diff).to.deep.equal([
{ d: "deleted", meta: this.meta },
{ u: "foo" },
{ i: "baz", meta: this.meta },
{ u: "bar" }
]);
});
});
return describe("a delete", function() {
describe("deleting unchanged text", function() {
it("should delete from the middle of (u)nchanged text", function() {
const diff = this.DiffGenerator.applyUpdateToDiff(
[ { u: "foobazbar" } ],
{ op: [{ p: 3, d: "baz" }], meta: this.meta }
);
return expect(diff).to.deep.equal([
{ u: "foo" },
{ d: "baz", meta: this.meta },
{ u: "bar" }
]);
});
it("should delete from the start of (u)nchanged text", function() {
const diff = this.DiffGenerator.applyUpdateToDiff(
[ { u: "foobazbar" } ],
{ op: [{ p: 0, d: "foo" }], meta: this.meta }
);
return expect(diff).to.deep.equal([
{ d: "foo", meta: this.meta },
{ u: "bazbar" }
]);
});
it("should delete from the end of (u)nchanged text", function() {
const diff = this.DiffGenerator.applyUpdateToDiff(
[ { u: "foobazbar" } ],
{ op: [{ p: 6, d: "bar" }], meta: this.meta }
);
return expect(diff).to.deep.equal([
{ u: "foobaz" },
{ d: "bar", meta: this.meta }
]);
});
return it("should delete across multiple (u)changed text parts", function() {
const diff = this.DiffGenerator.applyUpdateToDiff(
[ { u: "foo" }, { u: "baz" }, { u: "bar" } ],
{ op: [{ p: 2, d: "obazb" }], meta: this.meta }
);
return expect(diff).to.deep.equal([
{ u: "fo" },
{ d: "o", meta: this.meta },
{ d: "baz", meta: this.meta },
{ d: "b", meta: this.meta },
{ u: "ar" }
]);
});
});
describe("deleting inserts", function() {
it("should delete from the middle of (i)nserted text", function() {
const diff = this.DiffGenerator.applyUpdateToDiff(
[ { i: "foobazbar", meta: this.meta } ],
{ op: [{ p: 3, d: "baz" }], meta: this.meta }
);
return expect(diff).to.deep.equal([
{ i: "foo", meta: this.meta },
{ i: "bar", meta: this.meta }
]);
});
it("should delete from the start of (u)nchanged text", function() {
const diff = this.DiffGenerator.applyUpdateToDiff(
[ { i: "foobazbar", meta: this.meta } ],
{ op: [{ p: 0, d: "foo" }], meta: this.meta }
);
return expect(diff).to.deep.equal([
{ i: "bazbar", meta: this.meta }
]);
});
it("should delete from the end of (u)nchanged text", function() {
const diff = this.DiffGenerator.applyUpdateToDiff(
[ { i: "foobazbar", meta: this.meta } ],
{ op: [{ p: 6, d: "bar" }], meta: this.meta }
);
return expect(diff).to.deep.equal([
{ i: "foobaz", meta: this.meta }
]);
});
return it("should delete across multiple (u)changed and (i)nserted text parts", function() {
const diff = this.DiffGenerator.applyUpdateToDiff(
[ { u: "foo" }, { i: "baz", meta: this.meta }, { u: "bar" } ],
{ op: [{ p: 2, d: "obazb" }], meta: this.meta }
);
return expect(diff).to.deep.equal([
{ u: "fo" },
{ d: "o", meta: this.meta },
{ d: "b", meta: this.meta },
{ u: "ar" }
]);
});
});
describe("deleting over existing deletes", function() { return it("should delete across multiple (u)changed and (d)deleted text parts", function() {
const diff = this.DiffGenerator.applyUpdateToDiff(
[ { u: "foo" }, { d: "baz", meta: this.meta }, { u: "bar" } ],
{ op: [{ p: 2, d: "ob" }], meta: this.meta }
);
return expect(diff).to.deep.equal([
{ u: "fo" },
{ d: "o", meta: this.meta },
{ d: "baz", meta: this.meta },
{ d: "b", meta: this.meta },
{ u: "ar" }
]);
}); }
);
describe("deleting when the text doesn't match", function() {
it("should throw an error when deleting from the middle of (u)nchanged text", function() {
return expect(
() => this.DiffGenerator.applyUpdateToDiff(
[ { u: "foobazbar" } ],
{ op: [{ p: 3, d: "xxx" }], meta: this.meta }
)
).to.throw(this.DiffGenerator.ConsistencyError);
});
it("should throw an error when deleting from the start of (u)nchanged text", function() {
return expect(
() => this.DiffGenerator.applyUpdateToDiff(
[ { u: "foobazbar" } ],
{ op: [{ p: 0, d: "xxx" }], meta: this.meta }
)
).to.throw(this.DiffGenerator.ConsistencyError);
});
return it("should throw an error when deleting from the end of (u)nchanged text", function() {
return expect(
() => this.DiffGenerator.applyUpdateToDiff(
[ { u: "foobazbar" } ],
{ op: [{ p: 6, d: "xxx" }] , meta: this.meta }
)
).to.throw(this.DiffGenerator.ConsistencyError);
});
});
describe("when the last update in the existing diff is a delete", function() { return it("should insert the new update before the delete", function() {
const diff = this.DiffGenerator.applyUpdateToDiff(
[ { u: "foo" }, { d: "bar", meta: this.meta } ],
{ op: [{ p: 3, i: "baz" }], meta: this.meta }
);
return expect(diff).to.deep.equal([
{ u: "foo" },
{ i: "baz", meta: this.meta },
{ d: "bar", meta: this.meta }
]);
}); }
);
return describe("when the only update in the existing diff is a delete", function() { return it("should insert the new update after the delete", function() {
const diff = this.DiffGenerator.applyUpdateToDiff(
[ { d: "bar", meta: this.meta } ],
{ op: [{ p: 0, i: "baz" }], meta: this.meta }
);
return expect(diff).to.deep.equal([
{ d: "bar", meta: this.meta },
{ i: "baz", meta: this.meta }
]);
}); }
);
});
});
});