UpdateTranslatorTests.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923
  1. import sinon from 'sinon'
  2. import { expect } from 'chai'
  3. import { strict as esmock } from 'esmock'
  4. import Core from 'overleaf-editor-core'
  5. const MODULE_PATH = '../../../../app/js/UpdateTranslator.js'
  6. describe('UpdateTranslator', function () {
  7. beforeEach(async function () {
  8. this.UpdateTranslator = await esmock(MODULE_PATH, {
  9. 'overleaf-editor-core': Core,
  10. })
  11. this.callback = sinon.stub()
  12. this.project_id = '59bfd450e3028c4d40a1e9aa'
  13. this.doc_id = '59bfd450e3028c4d40a1e9ab'
  14. this.file_id = '59bfd450e3028c4d40a1easd'
  15. this.user_id = '59bb9051abf6e8682a269b64'
  16. this.version = 0
  17. this.timestamp = new Date().toJSON()
  18. this.mockBlobHash = '12345abc12345abc12345abc12345abc12345abc'
  19. })
  20. describe('convertToChanges', function () {
  21. it('can translate doc additions', function (done) {
  22. const updates = [
  23. {
  24. update: {
  25. doc: this.doc_id,
  26. pathname: '/main.tex',
  27. docLines: 'a\nb',
  28. meta: {
  29. user_id: this.user_id,
  30. ts: this.timestamp,
  31. },
  32. },
  33. blobHash: this.mockBlobHash,
  34. },
  35. ]
  36. const assertion = (error, changes) => {
  37. changes = changes.map(change => change.toRaw())
  38. expect(error).to.be.null
  39. expect(changes).to.deep.equal([
  40. {
  41. authors: [],
  42. operations: [
  43. {
  44. pathname: 'main.tex',
  45. file: {
  46. hash: this.mockBlobHash,
  47. },
  48. },
  49. ],
  50. v2Authors: [this.user_id],
  51. timestamp: this.timestamp,
  52. },
  53. ])
  54. done()
  55. }
  56. this.UpdateTranslator.convertToChanges(
  57. this.project_id,
  58. updates,
  59. assertion
  60. )
  61. })
  62. it('can translate file additions', function (done) {
  63. const updates = [
  64. {
  65. update: {
  66. file: this.file_id,
  67. pathname: '/test.png',
  68. url: 'filestore.example.com/test.png',
  69. meta: {
  70. user_id: this.user_id,
  71. ts: this.timestamp,
  72. },
  73. },
  74. blobHash: this.mockBlobHash,
  75. },
  76. ]
  77. const assertion = (error, changes) => {
  78. changes = changes.map(change => change.toRaw())
  79. expect(error).to.be.null
  80. expect(changes).to.deep.equal([
  81. {
  82. authors: [],
  83. operations: [
  84. {
  85. pathname: 'test.png',
  86. file: {
  87. hash: this.mockBlobHash,
  88. },
  89. },
  90. ],
  91. v2Authors: [this.user_id],
  92. timestamp: this.timestamp,
  93. },
  94. ])
  95. done()
  96. }
  97. this.UpdateTranslator.convertToChanges(
  98. this.project_id,
  99. updates,
  100. assertion
  101. )
  102. })
  103. it('can translate doc renames', function (done) {
  104. const updates = [
  105. {
  106. update: {
  107. doc: this.doc_id,
  108. pathname: '/main.tex',
  109. new_pathname: '/new_main.tex',
  110. meta: {
  111. user_id: this.user_id,
  112. ts: this.timestamp,
  113. },
  114. },
  115. },
  116. ]
  117. const assertion = (error, changes) => {
  118. changes = changes.map(change => change.toRaw())
  119. expect(error).to.be.null
  120. expect(changes).to.deep.equal([
  121. {
  122. authors: [],
  123. operations: [
  124. {
  125. pathname: 'main.tex',
  126. newPathname: 'new_main.tex',
  127. },
  128. ],
  129. v2Authors: [this.user_id],
  130. timestamp: this.timestamp,
  131. },
  132. ])
  133. done()
  134. }
  135. this.UpdateTranslator.convertToChanges(
  136. this.project_id,
  137. updates,
  138. assertion
  139. )
  140. })
  141. it('can translate file renames', function (done) {
  142. const updates = [
  143. {
  144. update: {
  145. file: this.file_id,
  146. pathname: '/test.png',
  147. new_pathname: '/new_test.png',
  148. meta: {
  149. user_id: this.user_id,
  150. ts: this.timestamp,
  151. },
  152. },
  153. },
  154. ]
  155. const assertion = (error, changes) => {
  156. changes = changes.map(change => change.toRaw())
  157. expect(error).to.be.null
  158. expect(changes).to.deep.equal([
  159. {
  160. authors: [],
  161. operations: [
  162. {
  163. pathname: 'test.png',
  164. newPathname: 'new_test.png',
  165. },
  166. ],
  167. v2Authors: [this.user_id],
  168. timestamp: this.timestamp,
  169. },
  170. ])
  171. done()
  172. }
  173. this.UpdateTranslator.convertToChanges(
  174. this.project_id,
  175. updates,
  176. assertion
  177. )
  178. })
  179. it('can translate multiple updates with the correct versions', function (done) {
  180. const updates = [
  181. {
  182. update: {
  183. doc: this.doc_id,
  184. pathname: '/main.tex',
  185. docLines: 'a\nb',
  186. meta: {
  187. user_id: this.user_id,
  188. ts: this.timestamp,
  189. },
  190. },
  191. blobHash: this.mockBlobHash,
  192. },
  193. {
  194. update: {
  195. file: this.file_id,
  196. pathname: '/test.png',
  197. url: 'filestore.example.com/test.png',
  198. meta: {
  199. user_id: this.user_id,
  200. ts: this.timestamp,
  201. },
  202. },
  203. blobHash: this.mockBlobHash,
  204. },
  205. ]
  206. const assertion = (error, changes) => {
  207. changes = changes.map(change => change.toRaw())
  208. expect(error).to.be.null
  209. expect(changes).to.deep.equal([
  210. {
  211. authors: [],
  212. operations: [
  213. {
  214. pathname: 'main.tex',
  215. file: {
  216. hash: this.mockBlobHash,
  217. },
  218. },
  219. ],
  220. v2Authors: [this.user_id],
  221. timestamp: this.timestamp,
  222. },
  223. {
  224. authors: [],
  225. operations: [
  226. {
  227. pathname: 'test.png',
  228. file: {
  229. hash: this.mockBlobHash,
  230. },
  231. },
  232. ],
  233. v2Authors: [this.user_id],
  234. timestamp: this.timestamp,
  235. },
  236. ])
  237. done()
  238. }
  239. this.UpdateTranslator.convertToChanges(
  240. this.project_id,
  241. updates,
  242. assertion
  243. )
  244. })
  245. it('returns an error if the update has an unknown format', function (done) {
  246. const updates = [
  247. {
  248. update: {
  249. foo: 'bar',
  250. },
  251. },
  252. ]
  253. const assertion = (error, changes) => {
  254. expect(error)
  255. .to.exist.and.be.instanceof(Error)
  256. .and.have.property('message', 'update with unknown format')
  257. done()
  258. }
  259. this.UpdateTranslator.convertToChanges(
  260. this.project_id,
  261. updates,
  262. assertion
  263. )
  264. })
  265. it('replaces backslashes with underscores in pathnames', function (done) {
  266. const updates = [
  267. {
  268. update: {
  269. doc: this.doc_id,
  270. pathname: '/\\main\\foo.tex',
  271. new_pathname: '/\\new_main\\foo\\bar.tex',
  272. meta: {
  273. user_id: this.user_id,
  274. ts: this.timestamp,
  275. },
  276. },
  277. },
  278. ]
  279. const assertion = (error, changes) => {
  280. changes = changes.map(change => change.toRaw())
  281. expect(error).to.be.null
  282. expect(changes).to.deep.equal([
  283. {
  284. authors: [],
  285. operations: [
  286. {
  287. pathname: '_main_foo.tex',
  288. newPathname: '_new_main_foo_bar.tex',
  289. },
  290. ],
  291. v2Authors: [this.user_id],
  292. timestamp: this.timestamp,
  293. },
  294. ])
  295. done()
  296. }
  297. this.UpdateTranslator.convertToChanges(
  298. this.project_id,
  299. updates,
  300. assertion
  301. )
  302. })
  303. it('replaces leading asterisks with __ASTERISK__ in pathnames', function (done) {
  304. const updates = [
  305. {
  306. update: {
  307. file: this.file_id,
  308. pathname: '/test*test.png',
  309. meta: {
  310. user_id: this.user_id,
  311. ts: this.timestamp,
  312. },
  313. url: 'filestore.example.com/test*test.png',
  314. },
  315. blobHash: this.mockBlobHash,
  316. },
  317. ]
  318. const assertion = (error, changes) => {
  319. changes = changes.map(change => change.toRaw())
  320. expect(error).to.be.null
  321. expect(changes).to.deep.equal([
  322. {
  323. authors: [],
  324. operations: [
  325. {
  326. pathname: 'test__ASTERISK__test.png',
  327. file: {
  328. hash: this.mockBlobHash,
  329. },
  330. },
  331. ],
  332. v2Authors: [this.user_id],
  333. timestamp: this.timestamp,
  334. },
  335. ])
  336. done()
  337. }
  338. this.UpdateTranslator.convertToChanges(
  339. this.project_id,
  340. updates,
  341. assertion
  342. )
  343. })
  344. it('replaces a leading space for top-level files with __SPACE__', function (done) {
  345. const updates = [
  346. {
  347. update: {
  348. file: this.file_id,
  349. pathname: '/ test.png',
  350. meta: {
  351. user_id: this.user_id,
  352. ts: this.timestamp,
  353. },
  354. url: 'filestore.example.com/test.png',
  355. },
  356. blobHash: this.mockBlobHash,
  357. },
  358. ]
  359. const assertion = (error, changes) => {
  360. changes = changes.map(change => change.toRaw())
  361. expect(error).to.be.null
  362. expect(changes).to.deep.equal([
  363. {
  364. authors: [],
  365. operations: [
  366. {
  367. pathname: '__SPACE__test.png',
  368. file: {
  369. hash: this.mockBlobHash,
  370. },
  371. },
  372. ],
  373. v2Authors: [this.user_id],
  374. timestamp: this.timestamp,
  375. },
  376. ])
  377. done()
  378. }
  379. this.UpdateTranslator.convertToChanges(
  380. this.project_id,
  381. updates,
  382. assertion
  383. )
  384. })
  385. it('replaces leading spaces of files in subfolders with __SPACE__', function (done) {
  386. const updates = [
  387. {
  388. update: {
  389. file: this.file_id,
  390. pathname: '/folder/ test.png',
  391. meta: {
  392. user_id: this.user_id,
  393. ts: this.timestamp,
  394. },
  395. url: 'filestore.example.com/folder/test.png',
  396. },
  397. blobHash: this.mockBlobHash,
  398. },
  399. ]
  400. const assertion = (error, changes) => {
  401. changes = changes.map(change => change.toRaw())
  402. expect(error).to.be.null
  403. expect(changes).to.deep.equal([
  404. {
  405. authors: [],
  406. operations: [
  407. {
  408. pathname: 'folder/__SPACE__test.png',
  409. file: {
  410. hash: this.mockBlobHash,
  411. },
  412. },
  413. ],
  414. v2Authors: [this.user_id],
  415. timestamp: this.timestamp,
  416. },
  417. ])
  418. done()
  419. }
  420. this.UpdateTranslator.convertToChanges(
  421. this.project_id,
  422. updates,
  423. assertion
  424. )
  425. })
  426. it('sets a null author when user_id is "anonymous-user"', function (done) {
  427. const updates = [
  428. {
  429. update: {
  430. doc: this.doc_id,
  431. pathname: '/main.tex',
  432. docLines: 'a\nb',
  433. meta: {
  434. user_id: 'anonymous-user',
  435. ts: this.timestamp,
  436. },
  437. },
  438. blobHash: this.mockBlobHash,
  439. },
  440. ]
  441. const assertion = (error, changes) => {
  442. changes = changes.map(change => change.toRaw())
  443. expect(error).to.be.null
  444. expect(changes).to.deep.equal([
  445. {
  446. authors: [],
  447. operations: [
  448. {
  449. pathname: 'main.tex',
  450. file: {
  451. hash: this.mockBlobHash,
  452. },
  453. },
  454. ],
  455. v2Authors: [null],
  456. timestamp: this.timestamp,
  457. },
  458. ])
  459. done()
  460. }
  461. this.UpdateTranslator.convertToChanges(
  462. this.project_id,
  463. updates,
  464. assertion
  465. )
  466. })
  467. it('sets an empty array as author when there is no meta.user_id', function (done) {
  468. const updates = [
  469. {
  470. update: {
  471. doc: this.doc_id,
  472. pathname: '/main.tex',
  473. docLines: 'a\nb',
  474. meta: {
  475. ts: this.timestamp,
  476. },
  477. },
  478. blobHash: this.mockBlobHash,
  479. },
  480. ]
  481. const assertion = (error, changes) => {
  482. changes = changes.map(change => change.toRaw())
  483. expect(error).to.be.null
  484. expect(changes).to.deep.equal([
  485. {
  486. authors: [],
  487. operations: [
  488. {
  489. pathname: 'main.tex',
  490. file: {
  491. hash: this.mockBlobHash,
  492. },
  493. },
  494. ],
  495. v2Authors: [],
  496. timestamp: this.timestamp,
  497. },
  498. ])
  499. done()
  500. }
  501. this.UpdateTranslator.convertToChanges(
  502. this.project_id,
  503. updates,
  504. assertion
  505. )
  506. })
  507. describe('text updates', function () {
  508. it('can translate insertions', function (done) {
  509. const updates = [
  510. {
  511. update: {
  512. doc: this.doc_id,
  513. op: [
  514. { p: 3, i: 'foo' },
  515. { p: 15, i: 'bar' },
  516. ],
  517. v: this.version,
  518. meta: {
  519. user_id: this.user_id,
  520. ts: new Date(this.timestamp).getTime(),
  521. pathname: '/main.tex',
  522. doc_length: 20,
  523. source: 'some-editor-id',
  524. },
  525. },
  526. },
  527. ]
  528. const assertion = (error, changes) => {
  529. changes = changes.map(change => change.toRaw())
  530. expect(error).to.be.null
  531. expect(changes).to.deep.equal([
  532. {
  533. authors: [],
  534. operations: [
  535. {
  536. pathname: 'main.tex',
  537. textOperation: [3, 'foo', 9, 'bar', 8],
  538. },
  539. ],
  540. v2Authors: [this.user_id],
  541. timestamp: this.timestamp,
  542. v2DocVersions: {
  543. '59bfd450e3028c4d40a1e9ab': {
  544. pathname: 'main.tex',
  545. v: 0,
  546. },
  547. },
  548. },
  549. ])
  550. done()
  551. }
  552. this.UpdateTranslator.convertToChanges(
  553. this.project_id,
  554. updates,
  555. assertion
  556. )
  557. })
  558. it('can translate deletions', function (done) {
  559. const updates = [
  560. {
  561. update: {
  562. doc: this.doc_id,
  563. op: [
  564. { p: 3, d: 'lo' },
  565. { p: 10, d: 'bar' },
  566. ],
  567. v: this.version,
  568. meta: {
  569. user_id: this.user_id,
  570. ts: new Date(this.timestamp).getTime(),
  571. pathname: '/main.tex',
  572. doc_length: 20,
  573. },
  574. },
  575. },
  576. ]
  577. const assertion = (error, changes) => {
  578. changes = changes.map(change => change.toRaw())
  579. expect(error).to.be.null
  580. expect(changes).to.deep.equal([
  581. {
  582. authors: [],
  583. operations: [
  584. {
  585. pathname: 'main.tex',
  586. textOperation: [3, -2, 7, -3, 5],
  587. },
  588. ],
  589. v2Authors: [this.user_id],
  590. timestamp: this.timestamp,
  591. v2DocVersions: {
  592. '59bfd450e3028c4d40a1e9ab': {
  593. pathname: 'main.tex',
  594. v: 0,
  595. },
  596. },
  597. },
  598. ])
  599. done()
  600. }
  601. this.UpdateTranslator.convertToChanges(
  602. this.project_id,
  603. updates,
  604. assertion
  605. )
  606. })
  607. it('can translate insertions at the start and end (with zero retained)', function (done) {
  608. const updates = [
  609. {
  610. update: {
  611. doc: this.doc_id,
  612. op: [
  613. { p: 0, i: 'foo' },
  614. { p: 23, i: 'bar' },
  615. { p: 0, d: 'foo' },
  616. { p: 20, d: 'bar' },
  617. ],
  618. v: this.version,
  619. meta: {
  620. user_id: this.user_id,
  621. ts: new Date(this.timestamp).getTime(),
  622. pathname: '/main.tex',
  623. doc_length: 20,
  624. },
  625. },
  626. },
  627. ]
  628. const assertion = (error, changes) => {
  629. changes = changes.map(change => change.toRaw())
  630. expect(error).to.be.null
  631. expect(changes).to.deep.equal([
  632. {
  633. authors: [],
  634. operations: [
  635. {
  636. pathname: 'main.tex',
  637. textOperation: [20],
  638. },
  639. ],
  640. v2Authors: [this.user_id],
  641. timestamp: this.timestamp,
  642. v2DocVersions: {
  643. '59bfd450e3028c4d40a1e9ab': {
  644. pathname: 'main.tex',
  645. v: 0,
  646. },
  647. },
  648. },
  649. ])
  650. done()
  651. }
  652. this.UpdateTranslator.convertToChanges(
  653. this.project_id,
  654. updates,
  655. assertion
  656. )
  657. })
  658. it('can handle operations in non-linear offset order', function (done) {
  659. const updates = [
  660. {
  661. update: {
  662. doc: this.doc_id,
  663. op: [
  664. { p: 15, i: 'foo' },
  665. { p: 3, i: 'bar' },
  666. ],
  667. v: this.version,
  668. meta: {
  669. user_id: this.user_id,
  670. ts: this.timestamp,
  671. pathname: '/main.tex',
  672. doc_length: 20,
  673. },
  674. },
  675. },
  676. ]
  677. const assertion = (error, changes) => {
  678. changes = changes.map(change => change.toRaw())
  679. expect(error).to.be.null
  680. expect(changes).to.deep.equal([
  681. {
  682. authors: [],
  683. operations: [
  684. {
  685. pathname: 'main.tex',
  686. textOperation: [3, 'bar', 12, 'foo', 5],
  687. },
  688. ],
  689. v2Authors: [this.user_id],
  690. timestamp: this.timestamp,
  691. v2DocVersions: {
  692. '59bfd450e3028c4d40a1e9ab': {
  693. pathname: 'main.tex',
  694. v: 0,
  695. },
  696. },
  697. },
  698. ])
  699. done()
  700. }
  701. this.UpdateTranslator.convertToChanges(
  702. this.project_id,
  703. updates,
  704. assertion
  705. )
  706. })
  707. it('can ignore comment ops', function (done) {
  708. const updates = [
  709. {
  710. update: {
  711. doc: this.doc_id,
  712. op: [
  713. { p: 0, i: 'foo' },
  714. { p: 5, c: 'bar' },
  715. { p: 10, i: 'baz' },
  716. ],
  717. v: this.version,
  718. meta: {
  719. user_id: this.user_id,
  720. ts: new Date(this.timestamp).getTime(),
  721. pathname: '/main.tex',
  722. doc_length: 20,
  723. },
  724. },
  725. },
  726. ]
  727. const assertion = (error, changes) => {
  728. changes = changes.map(change => change.toRaw())
  729. expect(error).to.be.null
  730. expect(changes).to.deep.equal([
  731. {
  732. authors: [],
  733. operations: [
  734. {
  735. pathname: 'main.tex',
  736. textOperation: ['foo', 7, 'baz', 13],
  737. },
  738. ],
  739. v2Authors: [this.user_id],
  740. timestamp: this.timestamp,
  741. v2DocVersions: {
  742. '59bfd450e3028c4d40a1e9ab': {
  743. pathname: 'main.tex',
  744. v: 0,
  745. },
  746. },
  747. },
  748. ])
  749. done()
  750. }
  751. this.UpdateTranslator.convertToChanges(
  752. this.project_id,
  753. updates,
  754. assertion
  755. )
  756. })
  757. it('handles insertions after the end of the document', function (done) {
  758. const updates = [
  759. {
  760. update: {
  761. doc: this.doc_id,
  762. op: [{ p: 3, i: '\\' }],
  763. v: this.version,
  764. meta: {
  765. user_id: this.user_id,
  766. ts: new Date(this.timestamp).getTime(),
  767. pathname: '/main.tex',
  768. doc_length: 2,
  769. },
  770. },
  771. },
  772. ]
  773. const assertion = (error, changes) => {
  774. changes = changes.map(change => change.toRaw())
  775. expect(error).to.be.null
  776. expect(changes).to.deep.equal([
  777. {
  778. authors: [],
  779. operations: [
  780. {
  781. pathname: 'main.tex',
  782. textOperation: [2, '\\'],
  783. },
  784. ],
  785. v2Authors: [this.user_id],
  786. timestamp: this.timestamp,
  787. v2DocVersions: {
  788. '59bfd450e3028c4d40a1e9ab': {
  789. pathname: 'main.tex',
  790. v: 0,
  791. },
  792. },
  793. },
  794. ])
  795. done()
  796. }
  797. this.UpdateTranslator.convertToChanges(
  798. this.project_id,
  799. updates,
  800. assertion
  801. )
  802. })
  803. it('translates external source metadata into an origin', function (done) {
  804. const updates = [
  805. {
  806. update: {
  807. doc: this.doc_id,
  808. op: [{ p: 3, i: 'foo' }],
  809. v: this.version,
  810. meta: {
  811. user_id: this.user_id,
  812. ts: new Date(this.timestamp).getTime(),
  813. pathname: '/main.tex',
  814. doc_length: 20,
  815. type: 'external',
  816. source: 'dropbox',
  817. },
  818. },
  819. },
  820. ]
  821. const assertion = (error, changes) => {
  822. changes = changes.map(change => change.toRaw())
  823. expect(error).to.be.null
  824. expect(changes).to.deep.equal([
  825. {
  826. authors: [],
  827. operations: [
  828. {
  829. pathname: 'main.tex',
  830. textOperation: [3, 'foo', 17],
  831. },
  832. ],
  833. v2Authors: [this.user_id],
  834. timestamp: this.timestamp,
  835. v2DocVersions: {
  836. '59bfd450e3028c4d40a1e9ab': {
  837. pathname: 'main.tex',
  838. v: 0,
  839. },
  840. },
  841. origin: { kind: 'dropbox' },
  842. },
  843. ])
  844. done()
  845. }
  846. this.UpdateTranslator.convertToChanges(
  847. this.project_id,
  848. updates,
  849. assertion
  850. )
  851. })
  852. it('errors on unexpected ops', function (done) {
  853. const updates = [
  854. {
  855. update: {
  856. doc: this.doc_id,
  857. op: [{ p: 5, z: 'bar' }],
  858. v: this.version,
  859. meta: {
  860. user_id: this.user_id,
  861. ts: new Date(this.timestamp).getTime(),
  862. pathname: '/main.tex',
  863. doc_length: 20,
  864. },
  865. },
  866. },
  867. ]
  868. const assertion = (error, changes) => {
  869. expect(error.message).to.equal('unexpected op type')
  870. done()
  871. }
  872. this.UpdateTranslator.convertToChanges(
  873. this.project_id,
  874. updates,
  875. assertion
  876. )
  877. })
  878. })
  879. })
  880. })