| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923 |
- import sinon from 'sinon'
- import { expect } from 'chai'
- import { strict as esmock } from 'esmock'
- import Core from 'overleaf-editor-core'
- const MODULE_PATH = '../../../../app/js/UpdateTranslator.js'
- describe('UpdateTranslator', function () {
- beforeEach(async function () {
- this.UpdateTranslator = await esmock(MODULE_PATH, {
- 'overleaf-editor-core': Core,
- })
- this.callback = sinon.stub()
- this.project_id = '59bfd450e3028c4d40a1e9aa'
- this.doc_id = '59bfd450e3028c4d40a1e9ab'
- this.file_id = '59bfd450e3028c4d40a1easd'
- this.user_id = '59bb9051abf6e8682a269b64'
- this.version = 0
- this.timestamp = new Date().toJSON()
- this.mockBlobHash = '12345abc12345abc12345abc12345abc12345abc'
- })
- describe('convertToChanges', function () {
- it('can translate doc additions', function (done) {
- const updates = [
- {
- update: {
- doc: this.doc_id,
- pathname: '/main.tex',
- docLines: 'a\nb',
- meta: {
- user_id: this.user_id,
- ts: this.timestamp,
- },
- },
- blobHash: this.mockBlobHash,
- },
- ]
- const assertion = (error, changes) => {
- changes = changes.map(change => change.toRaw())
- expect(error).to.be.null
- expect(changes).to.deep.equal([
- {
- authors: [],
- operations: [
- {
- pathname: 'main.tex',
- file: {
- hash: this.mockBlobHash,
- },
- },
- ],
- v2Authors: [this.user_id],
- timestamp: this.timestamp,
- },
- ])
- done()
- }
- this.UpdateTranslator.convertToChanges(
- this.project_id,
- updates,
- assertion
- )
- })
- it('can translate file additions', function (done) {
- const updates = [
- {
- update: {
- file: this.file_id,
- pathname: '/test.png',
- url: 'filestore.example.com/test.png',
- meta: {
- user_id: this.user_id,
- ts: this.timestamp,
- },
- },
- blobHash: this.mockBlobHash,
- },
- ]
- const assertion = (error, changes) => {
- changes = changes.map(change => change.toRaw())
- expect(error).to.be.null
- expect(changes).to.deep.equal([
- {
- authors: [],
- operations: [
- {
- pathname: 'test.png',
- file: {
- hash: this.mockBlobHash,
- },
- },
- ],
- v2Authors: [this.user_id],
- timestamp: this.timestamp,
- },
- ])
- done()
- }
- this.UpdateTranslator.convertToChanges(
- this.project_id,
- updates,
- assertion
- )
- })
- it('can translate doc renames', function (done) {
- const updates = [
- {
- update: {
- doc: this.doc_id,
- pathname: '/main.tex',
- new_pathname: '/new_main.tex',
- meta: {
- user_id: this.user_id,
- ts: this.timestamp,
- },
- },
- },
- ]
- const assertion = (error, changes) => {
- changes = changes.map(change => change.toRaw())
- expect(error).to.be.null
- expect(changes).to.deep.equal([
- {
- authors: [],
- operations: [
- {
- pathname: 'main.tex',
- newPathname: 'new_main.tex',
- },
- ],
- v2Authors: [this.user_id],
- timestamp: this.timestamp,
- },
- ])
- done()
- }
- this.UpdateTranslator.convertToChanges(
- this.project_id,
- updates,
- assertion
- )
- })
- it('can translate file renames', function (done) {
- const updates = [
- {
- update: {
- file: this.file_id,
- pathname: '/test.png',
- new_pathname: '/new_test.png',
- meta: {
- user_id: this.user_id,
- ts: this.timestamp,
- },
- },
- },
- ]
- const assertion = (error, changes) => {
- changes = changes.map(change => change.toRaw())
- expect(error).to.be.null
- expect(changes).to.deep.equal([
- {
- authors: [],
- operations: [
- {
- pathname: 'test.png',
- newPathname: 'new_test.png',
- },
- ],
- v2Authors: [this.user_id],
- timestamp: this.timestamp,
- },
- ])
- done()
- }
- this.UpdateTranslator.convertToChanges(
- this.project_id,
- updates,
- assertion
- )
- })
- it('can translate multiple updates with the correct versions', function (done) {
- const updates = [
- {
- update: {
- doc: this.doc_id,
- pathname: '/main.tex',
- docLines: 'a\nb',
- meta: {
- user_id: this.user_id,
- ts: this.timestamp,
- },
- },
- blobHash: this.mockBlobHash,
- },
- {
- update: {
- file: this.file_id,
- pathname: '/test.png',
- url: 'filestore.example.com/test.png',
- meta: {
- user_id: this.user_id,
- ts: this.timestamp,
- },
- },
- blobHash: this.mockBlobHash,
- },
- ]
- const assertion = (error, changes) => {
- changes = changes.map(change => change.toRaw())
- expect(error).to.be.null
- expect(changes).to.deep.equal([
- {
- authors: [],
- operations: [
- {
- pathname: 'main.tex',
- file: {
- hash: this.mockBlobHash,
- },
- },
- ],
- v2Authors: [this.user_id],
- timestamp: this.timestamp,
- },
- {
- authors: [],
- operations: [
- {
- pathname: 'test.png',
- file: {
- hash: this.mockBlobHash,
- },
- },
- ],
- v2Authors: [this.user_id],
- timestamp: this.timestamp,
- },
- ])
- done()
- }
- this.UpdateTranslator.convertToChanges(
- this.project_id,
- updates,
- assertion
- )
- })
- it('returns an error if the update has an unknown format', function (done) {
- const updates = [
- {
- update: {
- foo: 'bar',
- },
- },
- ]
- const assertion = (error, changes) => {
- expect(error)
- .to.exist.and.be.instanceof(Error)
- .and.have.property('message', 'update with unknown format')
- done()
- }
- this.UpdateTranslator.convertToChanges(
- this.project_id,
- updates,
- assertion
- )
- })
- it('replaces backslashes with underscores in pathnames', function (done) {
- const updates = [
- {
- update: {
- doc: this.doc_id,
- pathname: '/\\main\\foo.tex',
- new_pathname: '/\\new_main\\foo\\bar.tex',
- meta: {
- user_id: this.user_id,
- ts: this.timestamp,
- },
- },
- },
- ]
- const assertion = (error, changes) => {
- changes = changes.map(change => change.toRaw())
- expect(error).to.be.null
- expect(changes).to.deep.equal([
- {
- authors: [],
- operations: [
- {
- pathname: '_main_foo.tex',
- newPathname: '_new_main_foo_bar.tex',
- },
- ],
- v2Authors: [this.user_id],
- timestamp: this.timestamp,
- },
- ])
- done()
- }
- this.UpdateTranslator.convertToChanges(
- this.project_id,
- updates,
- assertion
- )
- })
- it('replaces leading asterisks with __ASTERISK__ in pathnames', function (done) {
- const updates = [
- {
- update: {
- file: this.file_id,
- pathname: '/test*test.png',
- meta: {
- user_id: this.user_id,
- ts: this.timestamp,
- },
- url: 'filestore.example.com/test*test.png',
- },
- blobHash: this.mockBlobHash,
- },
- ]
- const assertion = (error, changes) => {
- changes = changes.map(change => change.toRaw())
- expect(error).to.be.null
- expect(changes).to.deep.equal([
- {
- authors: [],
- operations: [
- {
- pathname: 'test__ASTERISK__test.png',
- file: {
- hash: this.mockBlobHash,
- },
- },
- ],
- v2Authors: [this.user_id],
- timestamp: this.timestamp,
- },
- ])
- done()
- }
- this.UpdateTranslator.convertToChanges(
- this.project_id,
- updates,
- assertion
- )
- })
- it('replaces a leading space for top-level files with __SPACE__', function (done) {
- const updates = [
- {
- update: {
- file: this.file_id,
- pathname: '/ test.png',
- meta: {
- user_id: this.user_id,
- ts: this.timestamp,
- },
- url: 'filestore.example.com/test.png',
- },
- blobHash: this.mockBlobHash,
- },
- ]
- const assertion = (error, changes) => {
- changes = changes.map(change => change.toRaw())
- expect(error).to.be.null
- expect(changes).to.deep.equal([
- {
- authors: [],
- operations: [
- {
- pathname: '__SPACE__test.png',
- file: {
- hash: this.mockBlobHash,
- },
- },
- ],
- v2Authors: [this.user_id],
- timestamp: this.timestamp,
- },
- ])
- done()
- }
- this.UpdateTranslator.convertToChanges(
- this.project_id,
- updates,
- assertion
- )
- })
- it('replaces leading spaces of files in subfolders with __SPACE__', function (done) {
- const updates = [
- {
- update: {
- file: this.file_id,
- pathname: '/folder/ test.png',
- meta: {
- user_id: this.user_id,
- ts: this.timestamp,
- },
- url: 'filestore.example.com/folder/test.png',
- },
- blobHash: this.mockBlobHash,
- },
- ]
- const assertion = (error, changes) => {
- changes = changes.map(change => change.toRaw())
- expect(error).to.be.null
- expect(changes).to.deep.equal([
- {
- authors: [],
- operations: [
- {
- pathname: 'folder/__SPACE__test.png',
- file: {
- hash: this.mockBlobHash,
- },
- },
- ],
- v2Authors: [this.user_id],
- timestamp: this.timestamp,
- },
- ])
- done()
- }
- this.UpdateTranslator.convertToChanges(
- this.project_id,
- updates,
- assertion
- )
- })
- it('sets a null author when user_id is "anonymous-user"', function (done) {
- const updates = [
- {
- update: {
- doc: this.doc_id,
- pathname: '/main.tex',
- docLines: 'a\nb',
- meta: {
- user_id: 'anonymous-user',
- ts: this.timestamp,
- },
- },
- blobHash: this.mockBlobHash,
- },
- ]
- const assertion = (error, changes) => {
- changes = changes.map(change => change.toRaw())
- expect(error).to.be.null
- expect(changes).to.deep.equal([
- {
- authors: [],
- operations: [
- {
- pathname: 'main.tex',
- file: {
- hash: this.mockBlobHash,
- },
- },
- ],
- v2Authors: [null],
- timestamp: this.timestamp,
- },
- ])
- done()
- }
- this.UpdateTranslator.convertToChanges(
- this.project_id,
- updates,
- assertion
- )
- })
- it('sets an empty array as author when there is no meta.user_id', function (done) {
- const updates = [
- {
- update: {
- doc: this.doc_id,
- pathname: '/main.tex',
- docLines: 'a\nb',
- meta: {
- ts: this.timestamp,
- },
- },
- blobHash: this.mockBlobHash,
- },
- ]
- const assertion = (error, changes) => {
- changes = changes.map(change => change.toRaw())
- expect(error).to.be.null
- expect(changes).to.deep.equal([
- {
- authors: [],
- operations: [
- {
- pathname: 'main.tex',
- file: {
- hash: this.mockBlobHash,
- },
- },
- ],
- v2Authors: [],
- timestamp: this.timestamp,
- },
- ])
- done()
- }
- this.UpdateTranslator.convertToChanges(
- this.project_id,
- updates,
- assertion
- )
- })
- describe('text updates', function () {
- it('can translate insertions', function (done) {
- const updates = [
- {
- update: {
- doc: this.doc_id,
- op: [
- { p: 3, i: 'foo' },
- { p: 15, i: 'bar' },
- ],
- v: this.version,
- meta: {
- user_id: this.user_id,
- ts: new Date(this.timestamp).getTime(),
- pathname: '/main.tex',
- doc_length: 20,
- source: 'some-editor-id',
- },
- },
- },
- ]
- const assertion = (error, changes) => {
- changes = changes.map(change => change.toRaw())
- expect(error).to.be.null
- expect(changes).to.deep.equal([
- {
- authors: [],
- operations: [
- {
- pathname: 'main.tex',
- textOperation: [3, 'foo', 9, 'bar', 8],
- },
- ],
- v2Authors: [this.user_id],
- timestamp: this.timestamp,
- v2DocVersions: {
- '59bfd450e3028c4d40a1e9ab': {
- pathname: 'main.tex',
- v: 0,
- },
- },
- },
- ])
- done()
- }
- this.UpdateTranslator.convertToChanges(
- this.project_id,
- updates,
- assertion
- )
- })
- it('can translate deletions', function (done) {
- const updates = [
- {
- update: {
- doc: this.doc_id,
- op: [
- { p: 3, d: 'lo' },
- { p: 10, d: 'bar' },
- ],
- v: this.version,
- meta: {
- user_id: this.user_id,
- ts: new Date(this.timestamp).getTime(),
- pathname: '/main.tex',
- doc_length: 20,
- },
- },
- },
- ]
- const assertion = (error, changes) => {
- changes = changes.map(change => change.toRaw())
- expect(error).to.be.null
- expect(changes).to.deep.equal([
- {
- authors: [],
- operations: [
- {
- pathname: 'main.tex',
- textOperation: [3, -2, 7, -3, 5],
- },
- ],
- v2Authors: [this.user_id],
- timestamp: this.timestamp,
- v2DocVersions: {
- '59bfd450e3028c4d40a1e9ab': {
- pathname: 'main.tex',
- v: 0,
- },
- },
- },
- ])
- done()
- }
- this.UpdateTranslator.convertToChanges(
- this.project_id,
- updates,
- assertion
- )
- })
- it('can translate insertions at the start and end (with zero retained)', function (done) {
- const updates = [
- {
- update: {
- doc: this.doc_id,
- op: [
- { p: 0, i: 'foo' },
- { p: 23, i: 'bar' },
- { p: 0, d: 'foo' },
- { p: 20, d: 'bar' },
- ],
- v: this.version,
- meta: {
- user_id: this.user_id,
- ts: new Date(this.timestamp).getTime(),
- pathname: '/main.tex',
- doc_length: 20,
- },
- },
- },
- ]
- const assertion = (error, changes) => {
- changes = changes.map(change => change.toRaw())
- expect(error).to.be.null
- expect(changes).to.deep.equal([
- {
- authors: [],
- operations: [
- {
- pathname: 'main.tex',
- textOperation: [20],
- },
- ],
- v2Authors: [this.user_id],
- timestamp: this.timestamp,
- v2DocVersions: {
- '59bfd450e3028c4d40a1e9ab': {
- pathname: 'main.tex',
- v: 0,
- },
- },
- },
- ])
- done()
- }
- this.UpdateTranslator.convertToChanges(
- this.project_id,
- updates,
- assertion
- )
- })
- it('can handle operations in non-linear offset order', function (done) {
- const updates = [
- {
- update: {
- doc: this.doc_id,
- op: [
- { p: 15, i: 'foo' },
- { p: 3, i: 'bar' },
- ],
- v: this.version,
- meta: {
- user_id: this.user_id,
- ts: this.timestamp,
- pathname: '/main.tex',
- doc_length: 20,
- },
- },
- },
- ]
- const assertion = (error, changes) => {
- changes = changes.map(change => change.toRaw())
- expect(error).to.be.null
- expect(changes).to.deep.equal([
- {
- authors: [],
- operations: [
- {
- pathname: 'main.tex',
- textOperation: [3, 'bar', 12, 'foo', 5],
- },
- ],
- v2Authors: [this.user_id],
- timestamp: this.timestamp,
- v2DocVersions: {
- '59bfd450e3028c4d40a1e9ab': {
- pathname: 'main.tex',
- v: 0,
- },
- },
- },
- ])
- done()
- }
- this.UpdateTranslator.convertToChanges(
- this.project_id,
- updates,
- assertion
- )
- })
- it('can ignore comment ops', function (done) {
- const updates = [
- {
- update: {
- doc: this.doc_id,
- op: [
- { p: 0, i: 'foo' },
- { p: 5, c: 'bar' },
- { p: 10, i: 'baz' },
- ],
- v: this.version,
- meta: {
- user_id: this.user_id,
- ts: new Date(this.timestamp).getTime(),
- pathname: '/main.tex',
- doc_length: 20,
- },
- },
- },
- ]
- const assertion = (error, changes) => {
- changes = changes.map(change => change.toRaw())
- expect(error).to.be.null
- expect(changes).to.deep.equal([
- {
- authors: [],
- operations: [
- {
- pathname: 'main.tex',
- textOperation: ['foo', 7, 'baz', 13],
- },
- ],
- v2Authors: [this.user_id],
- timestamp: this.timestamp,
- v2DocVersions: {
- '59bfd450e3028c4d40a1e9ab': {
- pathname: 'main.tex',
- v: 0,
- },
- },
- },
- ])
- done()
- }
- this.UpdateTranslator.convertToChanges(
- this.project_id,
- updates,
- assertion
- )
- })
- it('handles insertions after the end of the document', function (done) {
- const updates = [
- {
- update: {
- doc: this.doc_id,
- op: [{ p: 3, i: '\\' }],
- v: this.version,
- meta: {
- user_id: this.user_id,
- ts: new Date(this.timestamp).getTime(),
- pathname: '/main.tex',
- doc_length: 2,
- },
- },
- },
- ]
- const assertion = (error, changes) => {
- changes = changes.map(change => change.toRaw())
- expect(error).to.be.null
- expect(changes).to.deep.equal([
- {
- authors: [],
- operations: [
- {
- pathname: 'main.tex',
- textOperation: [2, '\\'],
- },
- ],
- v2Authors: [this.user_id],
- timestamp: this.timestamp,
- v2DocVersions: {
- '59bfd450e3028c4d40a1e9ab': {
- pathname: 'main.tex',
- v: 0,
- },
- },
- },
- ])
- done()
- }
- this.UpdateTranslator.convertToChanges(
- this.project_id,
- updates,
- assertion
- )
- })
- it('translates external source metadata into an origin', function (done) {
- const updates = [
- {
- update: {
- doc: this.doc_id,
- op: [{ p: 3, i: 'foo' }],
- v: this.version,
- meta: {
- user_id: this.user_id,
- ts: new Date(this.timestamp).getTime(),
- pathname: '/main.tex',
- doc_length: 20,
- type: 'external',
- source: 'dropbox',
- },
- },
- },
- ]
- const assertion = (error, changes) => {
- changes = changes.map(change => change.toRaw())
- expect(error).to.be.null
- expect(changes).to.deep.equal([
- {
- authors: [],
- operations: [
- {
- pathname: 'main.tex',
- textOperation: [3, 'foo', 17],
- },
- ],
- v2Authors: [this.user_id],
- timestamp: this.timestamp,
- v2DocVersions: {
- '59bfd450e3028c4d40a1e9ab': {
- pathname: 'main.tex',
- v: 0,
- },
- },
- origin: { kind: 'dropbox' },
- },
- ])
- done()
- }
- this.UpdateTranslator.convertToChanges(
- this.project_id,
- updates,
- assertion
- )
- })
- it('errors on unexpected ops', function (done) {
- const updates = [
- {
- update: {
- doc: this.doc_id,
- op: [{ p: 5, z: 'bar' }],
- v: this.version,
- meta: {
- user_id: this.user_id,
- ts: new Date(this.timestamp).getTime(),
- pathname: '/main.tex',
- doc_length: 20,
- },
- },
- },
- ]
- const assertion = (error, changes) => {
- expect(error.message).to.equal('unexpected op type')
- done()
- }
- this.UpdateTranslator.convertToChanges(
- this.project_id,
- updates,
- assertion
- )
- })
- })
- })
- })
|