ChunkTranslatorTests.js 49 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734
  1. import sinon from 'sinon'
  2. import { expect } from 'chai'
  3. import { strict as esmock } from 'esmock'
  4. const MODULE_PATH = '../../../../app/js/ChunkTranslator.js'
  5. describe('ChunkTranslator', function () {
  6. beforeEach(async function () {
  7. this.projectId = '0123456789abc0123456789abc'
  8. this.historyId = 12345
  9. this.author1 = {
  10. id: 1,
  11. email: 'james.allen@overleaf.com',
  12. name: 'James Allen',
  13. }
  14. this.date = new Date()
  15. this.fileHash = 'some_hash'
  16. this.fileContents = 'Hello world, this is a test'
  17. this.HistoryStoreManager = {
  18. getProjectBlob: sinon.stub(),
  19. }
  20. this.HistoryStoreManager.getProjectBlob
  21. .withArgs(this.historyId, this.fileHash)
  22. .yields(null, this.fileContents)
  23. this.WebApiManager = {
  24. getHistoryId: sinon.stub().callsFake((projectId, cb) => {
  25. console.log({ projectId })
  26. }),
  27. }
  28. this.WebApiManager.getHistoryId
  29. .withArgs(this.projectId)
  30. .yields(null, this.historyId)
  31. this.ChunkTranslator = await esmock(MODULE_PATH, {
  32. '../../../../app/js/HistoryStoreManager.js': this.HistoryStoreManager,
  33. '../../../../app/js/WebApiManager.js': this.WebApiManager,
  34. })
  35. this.callback = sinon.stub()
  36. })
  37. describe('with changes to the text', function () {
  38. beforeEach(function () {
  39. this.chunk = {
  40. project_id: this.projectId,
  41. chunk: {
  42. startVersion: 0,
  43. history: {
  44. snapshot: {
  45. files: {
  46. 'main.tex': {
  47. hash: this.fileHash,
  48. stringLength: 42,
  49. },
  50. },
  51. },
  52. changes: [
  53. {
  54. operations: [
  55. { pathname: 'main.tex', textOperation: ['Hello test, ', -6] },
  56. ],
  57. timestamp: this.date.toISOString(),
  58. authors: [this.author1.id],
  59. },
  60. {
  61. operations: [
  62. { pathname: 'main.tex', textOperation: [6, 'foo '] },
  63. ],
  64. timestamp: this.date.toISOString(),
  65. authors: [this.author1.id],
  66. },
  67. {
  68. operations: [{ pathname: 'main.tex', textOperation: [6, -4] }],
  69. timestamp: this.date.toISOString(),
  70. authors: [this.author1.id],
  71. },
  72. ],
  73. },
  74. },
  75. authors: [this.author1.id],
  76. }
  77. })
  78. describe('convertToDiffUpdates', function () {
  79. it('should convert them to insert and delete ops', function (done) {
  80. this.ChunkTranslator.convertToDiffUpdates(
  81. this.projectId,
  82. this.chunk,
  83. 'main.tex',
  84. 0,
  85. 3,
  86. (error, param) => {
  87. if (param == null) {
  88. param = {}
  89. }
  90. const { initialContent, updates } = param
  91. expect(error).to.be.null
  92. expect(initialContent).to.equal(this.fileContents)
  93. expect(updates).to.deep.equal([
  94. {
  95. op: [
  96. { i: 'Hello test, ', p: 0 },
  97. { d: 'Hello ', p: 12 },
  98. ],
  99. meta: {
  100. users: [this.author1.id],
  101. start_ts: this.date.getTime(),
  102. end_ts: this.date.getTime(),
  103. },
  104. v: 0,
  105. },
  106. {
  107. op: [{ i: 'foo ', p: 6 }],
  108. meta: {
  109. users: [this.author1.id],
  110. start_ts: this.date.getTime(),
  111. end_ts: this.date.getTime(),
  112. },
  113. v: 1,
  114. },
  115. {
  116. op: [{ d: 'foo ', p: 6 }],
  117. meta: {
  118. users: [this.author1.id],
  119. start_ts: this.date.getTime(),
  120. end_ts: this.date.getTime(),
  121. },
  122. v: 2,
  123. },
  124. ])
  125. done()
  126. }
  127. )
  128. })
  129. it('should return the correct initial text if there are previous changes', function (done) {
  130. this.ChunkTranslator.convertToDiffUpdates(
  131. this.projectId,
  132. this.chunk,
  133. 'main.tex',
  134. 2,
  135. 3,
  136. (error, param) => {
  137. if (param == null) {
  138. param = {}
  139. }
  140. const { initialContent, updates } = param
  141. expect(error).to.be.null
  142. expect(initialContent).to.equal(
  143. 'Hello foo test, world, this is a test'
  144. )
  145. expect(updates).to.deep.equal([
  146. {
  147. op: [{ d: 'foo ', p: 6 }],
  148. meta: {
  149. users: [this.author1.id],
  150. start_ts: this.date.getTime(),
  151. end_ts: this.date.getTime(),
  152. },
  153. v: 2,
  154. },
  155. ])
  156. done()
  157. }
  158. )
  159. })
  160. })
  161. describe('convertToSummarizedUpdates', function () {
  162. it('should return a summary of which docs changes when', function (done) {
  163. const assertion = (error, updates) => {
  164. expect(error).to.be.null
  165. expect(updates).to.deep.equal([
  166. {
  167. pathnames: ['main.tex'],
  168. project_ops: [],
  169. meta: {
  170. users: [this.author1.id],
  171. start_ts: this.date.getTime(),
  172. end_ts: this.date.getTime(),
  173. },
  174. v: 0,
  175. },
  176. {
  177. pathnames: ['main.tex'],
  178. project_ops: [],
  179. meta: {
  180. users: [this.author1.id],
  181. start_ts: this.date.getTime(),
  182. end_ts: this.date.getTime(),
  183. },
  184. v: 1,
  185. },
  186. {
  187. pathnames: ['main.tex'],
  188. project_ops: [],
  189. meta: {
  190. users: [this.author1.id],
  191. start_ts: this.date.getTime(),
  192. end_ts: this.date.getTime(),
  193. },
  194. v: 2,
  195. },
  196. ])
  197. done()
  198. }
  199. this.ChunkTranslator.convertToSummarizedUpdates(this.chunk, assertion)
  200. })
  201. })
  202. })
  203. describe('with a sequence of inserts and deletes', function () {
  204. beforeEach(function () {
  205. this.fileHash = 'some_other_hash'
  206. this.initialFileContents = 'aa bbbbb ccc '
  207. this.HistoryStoreManager.getProjectBlob
  208. .withArgs(this.historyId, this.fileHash)
  209. .yields(null, this.initialFileContents)
  210. this.chunk = {
  211. chunk: {
  212. startVersion: 0,
  213. history: {
  214. snapshot: {
  215. files: {
  216. 'main.tex': {
  217. hash: this.fileHash,
  218. stringLength: 42,
  219. },
  220. },
  221. },
  222. changes: [
  223. {
  224. operations: [
  225. {
  226. pathname: 'main.tex',
  227. textOperation: [
  228. '111 ', // -> "111 aa bbbbb ccc "
  229. -3, // -> "111 bbbbb ccc "
  230. 6, // -> "111 bbbbb ccc "
  231. '2222 ', // -> "111 bbbbb 2222 ccc "
  232. -1, // -> "111 bbbbb 2222 cc "
  233. 'd', // -> "111 bbbbb 2222 dcc "
  234. 3,
  235. ],
  236. },
  237. ],
  238. timestamp: this.date.toISOString(),
  239. authors: [this.author1.id],
  240. },
  241. ],
  242. },
  243. },
  244. authors: [this.author1.id],
  245. }
  246. })
  247. describe('convertToDiffUpdates', function () {
  248. it('should convert them to insert and delete ops', function (done) {
  249. this.ChunkTranslator.convertToDiffUpdates(
  250. this.projectId,
  251. this.chunk,
  252. 'main.tex',
  253. 0,
  254. 1,
  255. (error, param) => {
  256. if (param == null) {
  257. param = {}
  258. }
  259. const { initialContent, updates } = param
  260. expect(error).to.be.null
  261. expect(initialContent).to.equal(this.initialFileContents)
  262. expect(updates).to.deep.equal([
  263. {
  264. op: [
  265. { i: '111 ', p: 0 },
  266. { d: 'aa ', p: 4 },
  267. { i: '2222 ', p: 10 },
  268. { d: 'c', p: 15 },
  269. { i: 'd', p: 15 },
  270. ],
  271. meta: {
  272. users: [this.author1.id],
  273. start_ts: this.date.getTime(),
  274. end_ts: this.date.getTime(),
  275. },
  276. v: 0,
  277. },
  278. ])
  279. done()
  280. }
  281. )
  282. })
  283. it('should apply them to the text correctly', function (done) {
  284. this.ChunkTranslator.convertToDiffUpdates(
  285. this.projectId,
  286. this.chunk,
  287. 'main.tex',
  288. 1,
  289. 1,
  290. (error, param) => {
  291. if (param == null) {
  292. param = {}
  293. }
  294. const { initialContent, updates } = param
  295. expect(error).to.be.null
  296. expect(initialContent).to.equal('111 bbbbb 2222 dcc ')
  297. expect(updates).to.deep.equal([])
  298. done()
  299. }
  300. )
  301. })
  302. })
  303. })
  304. describe('with unknown operations', function (done) {
  305. beforeEach(function () {
  306. this.chunk = {
  307. chunk: {
  308. startVersion: 0,
  309. history: {
  310. snapshot: {
  311. files: {
  312. 'main.tex': {
  313. hash: this.fileHash,
  314. stringLength: 42,
  315. },
  316. },
  317. },
  318. changes: [
  319. {
  320. operations: [{ unknown: true }],
  321. timestamp: this.date.toISOString(),
  322. authors: [this.author1.id, undefined],
  323. },
  324. {
  325. operations: [
  326. { pathname: 'main.tex', textOperation: [3, 'Hello world'] },
  327. ],
  328. timestamp: this.date.toISOString(),
  329. authors: [this.author1.id, undefined],
  330. },
  331. ],
  332. },
  333. },
  334. authors: [this.author1.id],
  335. }
  336. })
  337. describe('convertToDiffUpdates', function () {
  338. it('should ignore the unknown update', function (done) {
  339. this.ChunkTranslator.convertToDiffUpdates(
  340. this.projectId,
  341. this.chunk,
  342. 'main.tex',
  343. 0,
  344. 2,
  345. (error, param) => {
  346. if (param == null) {
  347. param = {}
  348. }
  349. const { initialContent, updates } = param
  350. expect(error).to.be.null
  351. expect(initialContent).to.equal(this.fileContents)
  352. expect(updates).to.deep.equal([
  353. {
  354. op: [{ i: 'Hello world', p: 3 }],
  355. meta: {
  356. users: [this.author1.id, null],
  357. start_ts: this.date.getTime(),
  358. end_ts: this.date.getTime(),
  359. },
  360. v: 1,
  361. },
  362. ])
  363. done()
  364. }
  365. )
  366. })
  367. })
  368. describe('convertToSummarizedUpdates', function () {
  369. it('should ignore the unknown update', function (done) {
  370. const assertion = (error, updates) => {
  371. expect(error).to.be.null
  372. expect(updates).to.deep.equal([
  373. {
  374. pathnames: [],
  375. project_ops: [],
  376. meta: {
  377. users: [this.author1.id, null],
  378. start_ts: this.date.getTime(),
  379. end_ts: this.date.getTime(),
  380. },
  381. v: 0,
  382. },
  383. {
  384. pathnames: ['main.tex'],
  385. project_ops: [],
  386. meta: {
  387. users: [this.author1.id, null],
  388. start_ts: this.date.getTime(),
  389. end_ts: this.date.getTime(),
  390. },
  391. v: 1,
  392. },
  393. ])
  394. done()
  395. }
  396. this.ChunkTranslator.convertToSummarizedUpdates(this.chunk, assertion)
  397. })
  398. })
  399. })
  400. describe('with changes to multiple files', function (done) {
  401. beforeEach(function () {
  402. this.chunk = {
  403. chunk: {
  404. startVersion: 0,
  405. history: {
  406. snapshot: {
  407. files: {
  408. 'main.tex': {
  409. hash: this.fileHash,
  410. stringLength: 42,
  411. },
  412. 'other.tex': {
  413. hash: this.fileHash,
  414. stringLength: 42,
  415. },
  416. },
  417. },
  418. changes: [
  419. {
  420. operations: [
  421. { pathname: 'other.tex', textOperation: [0, 'foo'] },
  422. ],
  423. timestamp: this.date.toISOString(),
  424. authors: [this.author1.id],
  425. },
  426. {
  427. operations: [
  428. { pathname: 'main.tex', textOperation: [6, 'bar'] },
  429. ],
  430. timestamp: this.date.toISOString(),
  431. authors: [this.author1.id],
  432. },
  433. {
  434. operations: [
  435. { pathname: 'other.tex', textOperation: [9, 'baz'] },
  436. ],
  437. timestamp: this.date.toISOString(),
  438. authors: [this.author1.id],
  439. },
  440. {
  441. operations: [
  442. { pathname: 'main.tex', textOperation: [12, 'qux'] },
  443. ],
  444. timestamp: this.date.toISOString(),
  445. authors: [this.author1.id],
  446. },
  447. ],
  448. },
  449. },
  450. authors: [this.author1.id],
  451. }
  452. })
  453. describe('convertToDiffUpdates', function () {
  454. it('should only return the changes to the requested file', function (done) {
  455. this.ChunkTranslator.convertToDiffUpdates(
  456. this.projectId,
  457. this.chunk,
  458. 'main.tex',
  459. 0,
  460. 4,
  461. (error, param) => {
  462. if (param == null) {
  463. param = {}
  464. }
  465. const { initialContent, updates } = param
  466. expect(error).to.be.null
  467. expect(initialContent).to.equal('Hello world, this is a test')
  468. expect(updates).to.deep.equal([
  469. {
  470. op: [{ i: 'bar', p: 6 }],
  471. meta: {
  472. users: [this.author1.id],
  473. start_ts: this.date.getTime(),
  474. end_ts: this.date.getTime(),
  475. },
  476. v: 1,
  477. },
  478. {
  479. op: [{ i: 'qux', p: 12 }],
  480. meta: {
  481. users: [this.author1.id],
  482. start_ts: this.date.getTime(),
  483. end_ts: this.date.getTime(),
  484. },
  485. v: 3,
  486. },
  487. ])
  488. done()
  489. }
  490. )
  491. })
  492. })
  493. describe('convertToSummarizedUpdates', function () {
  494. it('should return a summary of which docs changes when', function (done) {
  495. const assertion = (error, updates) => {
  496. expect(error).to.be.null
  497. expect(updates).to.deep.equal([
  498. {
  499. pathnames: ['other.tex'],
  500. project_ops: [],
  501. meta: {
  502. users: [this.author1.id],
  503. start_ts: this.date.getTime(),
  504. end_ts: this.date.getTime(),
  505. },
  506. v: 0,
  507. },
  508. {
  509. pathnames: ['main.tex'],
  510. project_ops: [],
  511. meta: {
  512. users: [this.author1.id],
  513. start_ts: this.date.getTime(),
  514. end_ts: this.date.getTime(),
  515. },
  516. v: 1,
  517. },
  518. {
  519. pathnames: ['other.tex'],
  520. project_ops: [],
  521. meta: {
  522. users: [this.author1.id],
  523. start_ts: this.date.getTime(),
  524. end_ts: this.date.getTime(),
  525. },
  526. v: 2,
  527. },
  528. {
  529. pathnames: ['main.tex'],
  530. project_ops: [],
  531. meta: {
  532. users: [this.author1.id],
  533. start_ts: this.date.getTime(),
  534. end_ts: this.date.getTime(),
  535. },
  536. v: 3,
  537. },
  538. ])
  539. done()
  540. }
  541. this.ChunkTranslator.convertToSummarizedUpdates(this.chunk, assertion)
  542. })
  543. })
  544. })
  545. describe('when the file is created during the chunk', function () {
  546. beforeEach(function () {
  547. this.chunk = {
  548. chunk: {
  549. startVersion: 0,
  550. history: {
  551. snapshot: {
  552. files: {
  553. 'main.tex': {
  554. hash: this.fileHash,
  555. stringLength: 42,
  556. },
  557. },
  558. },
  559. changes: [
  560. {
  561. operations: [
  562. { pathname: 'main.tex', textOperation: [6, 'bar'] },
  563. ],
  564. timestamp: this.date.toISOString(),
  565. authors: [this.author1.id],
  566. },
  567. {
  568. operations: [
  569. {
  570. pathname: 'new.tex',
  571. file: { hash: this.fileHash, stringLength: 10 },
  572. },
  573. ],
  574. timestamp: this.date.toISOString(),
  575. authors: [this.author1.id],
  576. },
  577. {
  578. operations: [
  579. { pathname: 'new.tex', textOperation: [6, 'bar'] },
  580. ],
  581. timestamp: this.date.toISOString(),
  582. authors: [this.author1.id],
  583. },
  584. {
  585. operations: [
  586. { pathname: 'new.tex', textOperation: [9, 'baz'] },
  587. ],
  588. timestamp: this.date.toISOString(),
  589. authors: [this.author1.id],
  590. },
  591. ],
  592. },
  593. },
  594. authors: [this.author1],
  595. }
  596. })
  597. describe('convertToDiffUpdates', function () {
  598. it('returns changes after the file was created before the fromVersion', function (done) {
  599. this.ChunkTranslator.convertToDiffUpdates(
  600. this.projectId,
  601. this.chunk,
  602. 'new.tex',
  603. 2,
  604. 4,
  605. (error, param) => {
  606. if (param == null) {
  607. param = {}
  608. }
  609. const { initialContent, updates } = param
  610. expect(error).to.be.null
  611. expect(initialContent).to.equal(this.fileContents)
  612. expect(updates).to.deep.equal([
  613. {
  614. op: [{ i: 'bar', p: 6 }],
  615. meta: {
  616. users: [this.author1.id],
  617. start_ts: this.date.getTime(),
  618. end_ts: this.date.getTime(),
  619. },
  620. v: 2,
  621. },
  622. {
  623. op: [{ i: 'baz', p: 9 }],
  624. meta: {
  625. users: [this.author1.id],
  626. start_ts: this.date.getTime(),
  627. end_ts: this.date.getTime(),
  628. },
  629. v: 3,
  630. },
  631. ])
  632. done()
  633. }
  634. )
  635. })
  636. it('returns changes when the file was created at the fromVersion', function (done) {
  637. this.ChunkTranslator.convertToDiffUpdates(
  638. this.projectId,
  639. this.chunk,
  640. 'new.tex',
  641. 1,
  642. 4,
  643. (error, param) => {
  644. if (param == null) {
  645. param = {}
  646. }
  647. const { initialContent, updates } = param
  648. expect(error).to.be.null
  649. expect(initialContent).to.equal(this.fileContents)
  650. expect(updates).to.deep.equal([
  651. {
  652. op: [{ i: 'bar', p: 6 }],
  653. meta: {
  654. users: [this.author1.id],
  655. start_ts: this.date.getTime(),
  656. end_ts: this.date.getTime(),
  657. },
  658. v: 2,
  659. },
  660. {
  661. op: [{ i: 'baz', p: 9 }],
  662. meta: {
  663. users: [this.author1.id],
  664. start_ts: this.date.getTime(),
  665. end_ts: this.date.getTime(),
  666. },
  667. v: 3,
  668. },
  669. ])
  670. done()
  671. }
  672. )
  673. })
  674. it('returns changes when the file was created after the fromVersion', function (done) {
  675. this.ChunkTranslator.convertToDiffUpdates(
  676. this.projectId,
  677. this.chunk,
  678. 'new.tex',
  679. 0,
  680. 4,
  681. (error, param) => {
  682. if (param == null) {
  683. param = {}
  684. }
  685. const { initialContent, updates } = param
  686. expect(error).to.be.null
  687. expect(initialContent).to.equal(this.fileContents)
  688. expect(updates).to.deep.equal([
  689. {
  690. op: [{ i: 'bar', p: 6 }],
  691. meta: {
  692. users: [this.author1.id],
  693. start_ts: this.date.getTime(),
  694. end_ts: this.date.getTime(),
  695. },
  696. v: 2,
  697. },
  698. {
  699. op: [{ i: 'baz', p: 9 }],
  700. meta: {
  701. users: [this.author1.id],
  702. start_ts: this.date.getTime(),
  703. end_ts: this.date.getTime(),
  704. },
  705. v: 3,
  706. },
  707. ])
  708. done()
  709. }
  710. )
  711. })
  712. })
  713. describe('convertToSummarizedUpdates', function () {
  714. it('should return a summary which includes the addition', function (done) {
  715. const assertion = (error, updates) => {
  716. expect(error).to.be.null
  717. expect(updates).to.deep.equal([
  718. {
  719. pathnames: ['main.tex'],
  720. project_ops: [],
  721. meta: {
  722. users: [this.author1.id],
  723. start_ts: this.date.getTime(),
  724. end_ts: this.date.getTime(),
  725. },
  726. v: 0,
  727. },
  728. {
  729. pathnames: [],
  730. project_ops: [
  731. {
  732. add: {
  733. pathname: 'new.tex',
  734. },
  735. },
  736. ],
  737. meta: {
  738. users: [this.author1.id],
  739. start_ts: this.date.getTime(),
  740. end_ts: this.date.getTime(),
  741. },
  742. v: 1,
  743. },
  744. {
  745. pathnames: ['new.tex'],
  746. project_ops: [],
  747. meta: {
  748. users: [this.author1.id],
  749. start_ts: this.date.getTime(),
  750. end_ts: this.date.getTime(),
  751. },
  752. v: 2,
  753. },
  754. {
  755. pathnames: ['new.tex'],
  756. project_ops: [],
  757. meta: {
  758. users: [this.author1.id],
  759. start_ts: this.date.getTime(),
  760. end_ts: this.date.getTime(),
  761. },
  762. v: 3,
  763. },
  764. ])
  765. done()
  766. }
  767. this.ChunkTranslator.convertToSummarizedUpdates(this.chunk, assertion)
  768. })
  769. })
  770. })
  771. describe('when the file is renamed during the chunk', function () {
  772. beforeEach(function () {
  773. this.chunk = {
  774. chunk: {
  775. startVersion: 0,
  776. history: {
  777. snapshot: {
  778. files: {
  779. 'main.tex': {
  780. hash: this.fileHash,
  781. stringLength: 42,
  782. },
  783. },
  784. },
  785. changes: [
  786. {
  787. operations: [
  788. { pathname: 'main.tex', textOperation: [0, 'foo'] },
  789. ],
  790. timestamp: this.date.toISOString(),
  791. authors: [this.author1.id],
  792. },
  793. {
  794. operations: [
  795. { pathname: 'main.tex', newPathname: 'moved.tex' },
  796. ],
  797. timestamp: this.date.toISOString(),
  798. authors: [this.author1.id],
  799. },
  800. {
  801. operations: [
  802. { pathname: 'moved.tex', textOperation: [3, 'bar'] },
  803. ],
  804. timestamp: this.date.toISOString(),
  805. authors: [this.author1.id],
  806. },
  807. {
  808. operations: [
  809. { pathname: 'moved.tex', newPathname: 'moved_again.tex' },
  810. ],
  811. timestamp: this.date.toISOString(),
  812. authors: [this.author1.id],
  813. },
  814. {
  815. operations: [
  816. { pathname: 'moved_again.tex', textOperation: [6, 'baz'] },
  817. ],
  818. timestamp: this.date.toISOString(),
  819. authors: [this.author1.id],
  820. },
  821. ],
  822. },
  823. },
  824. authors: [this.author1],
  825. }
  826. })
  827. describe('convertToDiffUpdates', function () {
  828. it('uses the original pathname before it is moved', function (done) {
  829. this.ChunkTranslator.convertToDiffUpdates(
  830. this.projectId,
  831. this.chunk,
  832. 'main.tex',
  833. 0,
  834. 5,
  835. (error, param) => {
  836. if (param == null) {
  837. param = {}
  838. }
  839. const { initialContent, updates } = param
  840. expect(error).to.be.null
  841. expect(initialContent).to.equal(this.fileContents)
  842. expect(updates).to.deep.equal([
  843. {
  844. op: [{ i: 'foo', p: 0 }],
  845. meta: {
  846. users: [this.author1.id],
  847. start_ts: this.date.getTime(),
  848. end_ts: this.date.getTime(),
  849. },
  850. v: 0,
  851. },
  852. {
  853. op: [{ i: 'bar', p: 3 }],
  854. meta: {
  855. users: [this.author1.id],
  856. start_ts: this.date.getTime(),
  857. end_ts: this.date.getTime(),
  858. },
  859. v: 2,
  860. },
  861. {
  862. op: [{ i: 'baz', p: 6 }],
  863. meta: {
  864. users: [this.author1.id],
  865. start_ts: this.date.getTime(),
  866. end_ts: this.date.getTime(),
  867. },
  868. v: 4,
  869. },
  870. ])
  871. done()
  872. }
  873. )
  874. })
  875. it('uses the original pathname for before the move change', function (done) {
  876. this.ChunkTranslator.convertToDiffUpdates(
  877. this.projectId,
  878. this.chunk,
  879. 'main.tex',
  880. 1,
  881. 5,
  882. (error, param) => {
  883. if (param == null) {
  884. param = {}
  885. }
  886. const { initialContent, updates } = param
  887. expect(error).to.be.null
  888. expect(initialContent).to.equal('foo' + this.fileContents)
  889. expect(updates).to.deep.equal([
  890. {
  891. op: [{ i: 'bar', p: 3 }],
  892. meta: {
  893. users: [this.author1.id],
  894. start_ts: this.date.getTime(),
  895. end_ts: this.date.getTime(),
  896. },
  897. v: 2,
  898. },
  899. {
  900. op: [{ i: 'baz', p: 6 }],
  901. meta: {
  902. users: [this.author1.id],
  903. start_ts: this.date.getTime(),
  904. end_ts: this.date.getTime(),
  905. },
  906. v: 4,
  907. },
  908. ])
  909. done()
  910. }
  911. )
  912. })
  913. it('uses the new pathname for after the move change', function (done) {
  914. this.ChunkTranslator.convertToDiffUpdates(
  915. this.projectId,
  916. this.chunk,
  917. 'moved.tex',
  918. 2,
  919. 5,
  920. (error, param) => {
  921. if (param == null) {
  922. param = {}
  923. }
  924. const { initialContent, updates } = param
  925. expect(error).to.be.null
  926. expect(initialContent).to.equal('foo' + this.fileContents)
  927. expect(updates).to.deep.equal([
  928. {
  929. op: [{ i: 'bar', p: 3 }],
  930. meta: {
  931. users: [this.author1.id],
  932. start_ts: this.date.getTime(),
  933. end_ts: this.date.getTime(),
  934. },
  935. v: 2,
  936. },
  937. {
  938. op: [{ i: 'baz', p: 6 }],
  939. meta: {
  940. users: [this.author1.id],
  941. start_ts: this.date.getTime(),
  942. end_ts: this.date.getTime(),
  943. },
  944. v: 4,
  945. },
  946. ])
  947. done()
  948. }
  949. )
  950. })
  951. it('tracks multiple renames', function (done) {
  952. this.ChunkTranslator.convertToDiffUpdates(
  953. this.projectId,
  954. this.chunk,
  955. 'moved_again.tex',
  956. 4,
  957. 5,
  958. (error, param) => {
  959. if (param == null) {
  960. param = {}
  961. }
  962. const { initialContent, updates } = param
  963. expect(error).to.be.null
  964. expect(initialContent).to.equal('foobar' + this.fileContents)
  965. expect(updates).to.deep.equal([
  966. {
  967. op: [{ i: 'baz', p: 6 }],
  968. meta: {
  969. users: [this.author1.id],
  970. start_ts: this.date.getTime(),
  971. end_ts: this.date.getTime(),
  972. },
  973. v: 4,
  974. },
  975. ])
  976. done()
  977. }
  978. )
  979. })
  980. it('returns an error when referring to a file that is now moved', function (done) {
  981. this.ChunkTranslator.convertToDiffUpdates(
  982. this.projectId,
  983. this.chunk,
  984. 'main.tex',
  985. 4,
  986. 5,
  987. error => {
  988. expect(error.message).to.equal(
  989. "pathname 'main.tex' not found in range"
  990. )
  991. done()
  992. }
  993. )
  994. })
  995. })
  996. describe('convertToSummarizedUpdates', function () {
  997. it('should return a summary which includes the rename', function (done) {
  998. const assertion = (error, updates) => {
  999. expect(error).to.be.null
  1000. expect(updates).to.deep.equal([
  1001. {
  1002. pathnames: ['main.tex'],
  1003. project_ops: [],
  1004. meta: {
  1005. users: [this.author1.id],
  1006. start_ts: this.date.getTime(),
  1007. end_ts: this.date.getTime(),
  1008. },
  1009. v: 0,
  1010. },
  1011. {
  1012. pathnames: [],
  1013. project_ops: [
  1014. {
  1015. rename: {
  1016. pathname: 'main.tex',
  1017. newPathname: 'moved.tex',
  1018. },
  1019. },
  1020. ],
  1021. meta: {
  1022. users: [this.author1.id],
  1023. start_ts: this.date.getTime(),
  1024. end_ts: this.date.getTime(),
  1025. },
  1026. v: 1,
  1027. },
  1028. {
  1029. pathnames: ['moved.tex'],
  1030. project_ops: [],
  1031. meta: {
  1032. users: [this.author1.id],
  1033. start_ts: this.date.getTime(),
  1034. end_ts: this.date.getTime(),
  1035. },
  1036. v: 2,
  1037. },
  1038. {
  1039. pathnames: [],
  1040. project_ops: [
  1041. {
  1042. rename: {
  1043. pathname: 'moved.tex',
  1044. newPathname: 'moved_again.tex',
  1045. },
  1046. },
  1047. ],
  1048. meta: {
  1049. users: [this.author1.id],
  1050. start_ts: this.date.getTime(),
  1051. end_ts: this.date.getTime(),
  1052. },
  1053. v: 3,
  1054. },
  1055. {
  1056. pathnames: ['moved_again.tex'],
  1057. project_ops: [],
  1058. meta: {
  1059. users: [this.author1.id],
  1060. start_ts: this.date.getTime(),
  1061. end_ts: this.date.getTime(),
  1062. },
  1063. v: 4,
  1064. },
  1065. ])
  1066. done()
  1067. }
  1068. this.ChunkTranslator.convertToSummarizedUpdates(this.chunk, assertion)
  1069. })
  1070. })
  1071. })
  1072. describe('when the file is deleted during the chunk', function () {
  1073. beforeEach(function () {
  1074. this.chunk = {
  1075. chunk: {
  1076. startVersion: 0,
  1077. history: {
  1078. snapshot: {
  1079. files: {
  1080. 'main.tex': {
  1081. hash: this.fileHash,
  1082. stringLength: 42,
  1083. },
  1084. 'other.tex': {
  1085. hash: this.fileHash,
  1086. stringLength: 42,
  1087. },
  1088. },
  1089. },
  1090. changes: [
  1091. {
  1092. operations: [
  1093. { pathname: 'main.tex', textOperation: [0, 'foo'] },
  1094. ],
  1095. timestamp: this.date.toISOString(),
  1096. authors: [this.author1.id],
  1097. },
  1098. {
  1099. operations: [{ pathname: 'main.tex', newPathname: '' }],
  1100. timestamp: this.date.toISOString(),
  1101. authors: [this.author1.id],
  1102. },
  1103. {
  1104. operations: [
  1105. { pathname: 'other.tex', textOperation: [0, 'foo'] },
  1106. ],
  1107. timestamp: this.date.toISOString(),
  1108. authors: [this.author1.id],
  1109. },
  1110. ],
  1111. },
  1112. },
  1113. authors: [this.author1],
  1114. }
  1115. })
  1116. describe('convertToDiffUpdates', function () {
  1117. it('returns updates up to when it is deleted', function (done) {
  1118. this.ChunkTranslator.convertToDiffUpdates(
  1119. this.projectId,
  1120. this.chunk,
  1121. 'main.tex',
  1122. 0,
  1123. 3,
  1124. (error, param) => {
  1125. if (param == null) {
  1126. param = {}
  1127. }
  1128. const { initialContent, updates } = param
  1129. expect(error).to.be.null
  1130. expect(initialContent).to.equal(this.fileContents)
  1131. expect(updates).to.deep.equal([
  1132. {
  1133. op: [{ i: 'foo', p: 0 }],
  1134. meta: {
  1135. users: [this.author1.id],
  1136. start_ts: this.date.getTime(),
  1137. end_ts: this.date.getTime(),
  1138. },
  1139. v: 0,
  1140. },
  1141. ])
  1142. done()
  1143. }
  1144. )
  1145. })
  1146. it('returns nothing if fromVersion is when is it was deleted', function (done) {
  1147. this.ChunkTranslator.convertToDiffUpdates(
  1148. this.projectId,
  1149. this.chunk,
  1150. 'main.tex',
  1151. 1,
  1152. 3,
  1153. (error, param) => {
  1154. if (param == null) {
  1155. param = {}
  1156. }
  1157. const { initialContent, updates } = param
  1158. expect(error).to.be.null
  1159. expect(initialContent).to.equal('foo' + this.fileContents)
  1160. expect(updates).to.deep.equal([])
  1161. done()
  1162. }
  1163. )
  1164. })
  1165. it('returns an error requesting changes after deleted', function (done) {
  1166. this.ChunkTranslator.convertToDiffUpdates(
  1167. this.projectId,
  1168. this.chunk,
  1169. 'main.tex',
  1170. 2,
  1171. 3,
  1172. error => {
  1173. expect(error.message).to.equal(
  1174. "pathname 'main.tex' not found in range"
  1175. )
  1176. done()
  1177. }
  1178. )
  1179. })
  1180. })
  1181. describe('convertToSummarizedUpdates', function () {
  1182. it('should return a summary which includes the delete', function (done) {
  1183. const assertion = (error, updates) => {
  1184. expect(error).to.be.null
  1185. expect(updates).to.deep.equal([
  1186. {
  1187. pathnames: ['main.tex'],
  1188. project_ops: [],
  1189. meta: {
  1190. users: [this.author1.id],
  1191. start_ts: this.date.getTime(),
  1192. end_ts: this.date.getTime(),
  1193. },
  1194. v: 0,
  1195. },
  1196. {
  1197. pathnames: [],
  1198. project_ops: [
  1199. {
  1200. remove: {
  1201. pathname: 'main.tex',
  1202. },
  1203. },
  1204. ],
  1205. meta: {
  1206. users: [this.author1.id],
  1207. start_ts: this.date.getTime(),
  1208. end_ts: this.date.getTime(),
  1209. },
  1210. v: 1,
  1211. },
  1212. {
  1213. pathnames: ['other.tex'],
  1214. project_ops: [],
  1215. meta: {
  1216. users: [this.author1.id],
  1217. start_ts: this.date.getTime(),
  1218. end_ts: this.date.getTime(),
  1219. },
  1220. v: 2,
  1221. },
  1222. ])
  1223. done()
  1224. }
  1225. this.ChunkTranslator.convertToSummarizedUpdates(this.chunk, assertion)
  1226. })
  1227. })
  1228. })
  1229. describe("with text operations applied to files that don't exist", function (done) {
  1230. beforeEach(function () {
  1231. this.chunk = {
  1232. chunk: {
  1233. startVersion: 0,
  1234. history: {
  1235. snapshot: {
  1236. files: {
  1237. 'main.tex': {
  1238. hash: this.fileHash,
  1239. stringLength: 42,
  1240. },
  1241. },
  1242. },
  1243. changes: [
  1244. {
  1245. operations: [
  1246. {
  1247. pathname: 'not_here.tex',
  1248. textOperation: [3, 'Hello world'],
  1249. },
  1250. ],
  1251. timestamp: this.date.toISOString(),
  1252. authors: [this.author1.id, undefined],
  1253. },
  1254. ],
  1255. },
  1256. },
  1257. authors: [this.author1],
  1258. }
  1259. })
  1260. describe('convertToSummarizedUpdates', function () {
  1261. it('should return an empty diff instead of an error', function (done) {
  1262. this.ChunkTranslator.convertToDiffUpdates(
  1263. this.projectId,
  1264. this.chunk,
  1265. 'not_here.tex',
  1266. 0,
  1267. 1,
  1268. (error, result) => {
  1269. expect(error).to.equal(null)
  1270. expect(result.updates.length).to.equal(0)
  1271. done()
  1272. }
  1273. )
  1274. })
  1275. })
  1276. })
  1277. describe("with rename operations applied to files that don't exist", function (done) {
  1278. beforeEach(function () {
  1279. this.chunk = {
  1280. chunk: {
  1281. startVersion: 0,
  1282. history: {
  1283. snapshot: {
  1284. files: {
  1285. 'main.tex': {
  1286. hash: this.fileHash,
  1287. stringLength: 42,
  1288. },
  1289. },
  1290. },
  1291. changes: [
  1292. {
  1293. operations: [
  1294. { pathname: 'not_here.tex', newPathname: 'blah.tex' },
  1295. ],
  1296. timestamp: this.date.toISOString(),
  1297. authors: [this.author1.id, undefined],
  1298. },
  1299. ],
  1300. },
  1301. },
  1302. authors: [this.author1],
  1303. }
  1304. })
  1305. describe('convertToSummarizedUpdates', function () {
  1306. it('should return an empty diff instead of an error', function (done) {
  1307. this.ChunkTranslator.convertToDiffUpdates(
  1308. this.projectId,
  1309. this.chunk,
  1310. 'not_here.tex',
  1311. 0,
  1312. 1,
  1313. (error, result) => {
  1314. expect(error).to.equal(null)
  1315. expect(result.updates.length).to.equal(0)
  1316. done()
  1317. }
  1318. )
  1319. })
  1320. })
  1321. })
  1322. describe("with remove operations applied to files that don't exist", function (done) {
  1323. beforeEach(function () {
  1324. this.chunk = {
  1325. chunk: {
  1326. startVersion: 0,
  1327. history: {
  1328. snapshot: {
  1329. files: {
  1330. 'main.tex': {
  1331. hash: this.fileHash,
  1332. stringLength: 42,
  1333. },
  1334. },
  1335. },
  1336. changes: [
  1337. {
  1338. operations: [{ pathname: 'not_here.tex', newPathname: '' }],
  1339. timestamp: this.date.toISOString(),
  1340. authors: [this.author1.id, undefined],
  1341. },
  1342. ],
  1343. },
  1344. },
  1345. authors: [this.author1],
  1346. }
  1347. })
  1348. describe('convertToSummarizedUpdates', function () {
  1349. it('should return an empty diff instead of an error', function (done) {
  1350. this.ChunkTranslator.convertToDiffUpdates(
  1351. this.projectId,
  1352. this.chunk,
  1353. 'not_here.tex',
  1354. 0,
  1355. 1,
  1356. (error, result) => {
  1357. expect(error).to.equal(null)
  1358. expect(result.updates.length).to.equal(0)
  1359. done()
  1360. }
  1361. )
  1362. })
  1363. })
  1364. })
  1365. describe('with multiple operations in one change', function () {
  1366. beforeEach(function () {
  1367. this.chunk = {
  1368. chunk: {
  1369. startVersion: 0,
  1370. history: {
  1371. snapshot: {
  1372. files: {
  1373. 'main.tex': {
  1374. hash: this.fileHash,
  1375. stringLength: 42,
  1376. },
  1377. 'other.tex': {
  1378. hash: this.fileHash,
  1379. stringLength: 42,
  1380. },
  1381. 'old.tex': {
  1382. hash: this.fileHash,
  1383. stringLength: 42,
  1384. },
  1385. 'deleted.tex': {
  1386. hash: this.fileHash,
  1387. stringLength: 42,
  1388. },
  1389. },
  1390. },
  1391. changes: [
  1392. {
  1393. operations: [
  1394. { pathname: 'main.tex', textOperation: ['Hello test, ', -6] },
  1395. { pathname: 'main.tex', textOperation: [6, 'foo '] },
  1396. { pathname: 'other.tex', textOperation: [6, 'foo '] },
  1397. { pathname: 'old.tex', newPathname: 'new.tex' },
  1398. { pathname: 'deleted.tex', newPathname: '' },
  1399. {
  1400. pathname: 'created.tex',
  1401. file: { hash: this.fileHash, stringLength: 10 },
  1402. },
  1403. ],
  1404. timestamp: this.date.toISOString(),
  1405. authors: [this.author1.id],
  1406. },
  1407. {
  1408. operations: [{ pathname: 'main.tex', textOperation: [6, -4] }],
  1409. timestamp: this.date.toISOString(),
  1410. authors: [this.author1.id],
  1411. },
  1412. ],
  1413. },
  1414. },
  1415. authors: [this.author1],
  1416. }
  1417. })
  1418. describe('convertToDiffUpdates', function () {
  1419. it('should can return multiple ops from the same version', function (done) {
  1420. this.ChunkTranslator.convertToDiffUpdates(
  1421. this.projectId,
  1422. this.chunk,
  1423. 'main.tex',
  1424. 0,
  1425. 2,
  1426. (error, param) => {
  1427. if (param == null) {
  1428. param = {}
  1429. }
  1430. const { initialContent, updates } = param
  1431. expect(error).to.be.null
  1432. expect(initialContent).to.equal(this.fileContents)
  1433. expect(updates).to.deep.equal([
  1434. {
  1435. op: [
  1436. { i: 'Hello test, ', p: 0 },
  1437. { d: 'Hello ', p: 12 },
  1438. ],
  1439. meta: {
  1440. users: [this.author1.id],
  1441. start_ts: this.date.getTime(),
  1442. end_ts: this.date.getTime(),
  1443. },
  1444. v: 0,
  1445. },
  1446. {
  1447. op: [{ i: 'foo ', p: 6 }],
  1448. meta: {
  1449. users: [this.author1.id],
  1450. start_ts: this.date.getTime(),
  1451. end_ts: this.date.getTime(),
  1452. },
  1453. v: 0,
  1454. },
  1455. {
  1456. op: [{ d: 'foo ', p: 6 }],
  1457. meta: {
  1458. users: [this.author1.id],
  1459. start_ts: this.date.getTime(),
  1460. end_ts: this.date.getTime(),
  1461. },
  1462. v: 1,
  1463. },
  1464. ])
  1465. done()
  1466. }
  1467. )
  1468. })
  1469. it('should return the correct initial text if there are previous changes', function (done) {
  1470. this.ChunkTranslator.convertToDiffUpdates(
  1471. this.projectId,
  1472. this.chunk,
  1473. 'main.tex',
  1474. 1,
  1475. 2,
  1476. (error, param) => {
  1477. if (param == null) {
  1478. param = {}
  1479. }
  1480. const { initialContent, updates } = param
  1481. expect(error).to.be.null
  1482. expect(initialContent).to.equal(
  1483. 'Hello foo test, world, this is a test'
  1484. )
  1485. expect(updates).to.deep.equal([
  1486. {
  1487. op: [{ d: 'foo ', p: 6 }],
  1488. meta: {
  1489. users: [this.author1.id],
  1490. start_ts: this.date.getTime(),
  1491. end_ts: this.date.getTime(),
  1492. },
  1493. v: 1,
  1494. },
  1495. ])
  1496. done()
  1497. }
  1498. )
  1499. })
  1500. })
  1501. describe('convertToSummarizedUpdates', function () {
  1502. it('should return a summary of containing multiple changes', function (done) {
  1503. const assertion = (error, updates) => {
  1504. expect(error).to.be.null
  1505. expect(updates).to.deep.equal([
  1506. {
  1507. pathnames: ['main.tex', 'other.tex'],
  1508. project_ops: [
  1509. {
  1510. rename: {
  1511. pathname: 'old.tex',
  1512. newPathname: 'new.tex',
  1513. },
  1514. },
  1515. {
  1516. remove: {
  1517. pathname: 'deleted.tex',
  1518. },
  1519. },
  1520. {
  1521. add: {
  1522. pathname: 'created.tex',
  1523. },
  1524. },
  1525. ],
  1526. meta: {
  1527. users: [this.author1.id],
  1528. start_ts: this.date.getTime(),
  1529. end_ts: this.date.getTime(),
  1530. },
  1531. v: 0,
  1532. },
  1533. {
  1534. pathnames: ['main.tex'],
  1535. project_ops: [],
  1536. meta: {
  1537. users: [this.author1.id],
  1538. start_ts: this.date.getTime(),
  1539. end_ts: this.date.getTime(),
  1540. },
  1541. v: 1,
  1542. },
  1543. ])
  1544. done()
  1545. }
  1546. this.ChunkTranslator.convertToSummarizedUpdates(this.chunk, assertion)
  1547. })
  1548. })
  1549. })
  1550. describe('with a binary file', function () {
  1551. beforeEach(function () {
  1552. this.chunk = {
  1553. chunk: {
  1554. startVersion: 0,
  1555. history: {
  1556. snapshot: {
  1557. files: {
  1558. 'main.tex': {
  1559. hash: this.fileHash,
  1560. stringLength: 42,
  1561. },
  1562. 'binary.tex': {
  1563. hash: this.fileHash,
  1564. byteLength: 42,
  1565. },
  1566. },
  1567. },
  1568. changes: [
  1569. {
  1570. operations: [
  1571. { pathname: 'main.tex', textOperation: ['Hello test, ', -6] },
  1572. ],
  1573. timestamp: this.date.toISOString(),
  1574. authors: [this.author1.id],
  1575. },
  1576. ],
  1577. },
  1578. },
  1579. authors: [this.author1.id],
  1580. }
  1581. })
  1582. describe('convertToDiffUpdates', function () {
  1583. it('should convert them to a binary diff', function (done) {
  1584. this.ChunkTranslator.convertToDiffUpdates(
  1585. this.projectId,
  1586. this.chunk,
  1587. 'binary.tex',
  1588. 0,
  1589. 1,
  1590. (error, diff) => {
  1591. expect(error).to.be.null
  1592. expect(diff).to.deep.equal({ binary: true })
  1593. done()
  1594. }
  1595. )
  1596. })
  1597. })
  1598. })
  1599. describe('with v2 author ids', function () {
  1600. beforeEach(function () {
  1601. this.chunk = {
  1602. chunk: {
  1603. startVersion: 0,
  1604. history: {
  1605. snapshot: {
  1606. files: {
  1607. 'main.tex': {
  1608. hash: this.fileHash,
  1609. stringLength: 42,
  1610. },
  1611. },
  1612. },
  1613. changes: [
  1614. {
  1615. operations: [
  1616. { pathname: 'main.tex', textOperation: ['Hello test, ', -6] },
  1617. ],
  1618. timestamp: this.date.toISOString(),
  1619. v2Authors: [(this.v2AuthorId = '123456789')],
  1620. },
  1621. ],
  1622. },
  1623. },
  1624. }
  1625. })
  1626. describe('convertToDiffUpdates', function () {
  1627. it('should return the v2 author id in the users array', function (done) {
  1628. this.ChunkTranslator.convertToDiffUpdates(
  1629. this.projectId,
  1630. this.chunk,
  1631. 'main.tex',
  1632. 0,
  1633. 1,
  1634. (error, diff) => {
  1635. expect(error).to.be.null
  1636. expect(diff.updates).to.deep.equal([
  1637. {
  1638. op: [
  1639. { i: 'Hello test, ', p: 0 },
  1640. { d: 'Hello ', p: 12 },
  1641. ],
  1642. meta: {
  1643. users: [this.v2AuthorId],
  1644. start_ts: this.date.getTime(),
  1645. end_ts: this.date.getTime(),
  1646. },
  1647. v: 0,
  1648. },
  1649. ])
  1650. done()
  1651. }
  1652. )
  1653. })
  1654. })
  1655. describe('convertToSummarizedUpdates', function () {
  1656. it('should return the v2 author id in the users array', function (done) {
  1657. const assertion = (error, updateSet) => {
  1658. expect(error).to.be.null
  1659. expect(updateSet).to.deep.equal([
  1660. {
  1661. pathnames: ['main.tex'],
  1662. project_ops: [],
  1663. meta: {
  1664. users: [this.v2AuthorId],
  1665. start_ts: this.date.getTime(),
  1666. end_ts: this.date.getTime(),
  1667. },
  1668. v: 0,
  1669. },
  1670. ])
  1671. done()
  1672. }
  1673. this.ChunkTranslator.convertToSummarizedUpdates(this.chunk, assertion)
  1674. })
  1675. })
  1676. })
  1677. })