EditorControllerTests.js 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089
  1. /* eslint-disable
  2. n/handle-callback-err,
  3. max-len,
  4. no-return-assign,
  5. no-unused-vars,
  6. */
  7. // TODO: This file was created by bulk-decaffeinate.
  8. // Fix any style issues and re-enable lint.
  9. /*
  10. * decaffeinate suggestions:
  11. * DS102: Remove unnecessary code created because of implicit returns
  12. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  13. */
  14. const SandboxedModule = require('sandboxed-module')
  15. const sinon = require('sinon')
  16. const { expect } = require('chai')
  17. const OError = require('@overleaf/o-error')
  18. const modulePath = require('path').join(
  19. __dirname,
  20. '../../../../app/src/Features/Editor/EditorController'
  21. )
  22. const MockClient = require('../helpers/MockClient')
  23. const assert = require('assert')
  24. const { ObjectId } = require('mongodb-legacy')
  25. describe('EditorController', function () {
  26. beforeEach(function () {
  27. this.project_id = 'test-project-id'
  28. this.source = 'dropbox'
  29. this.user_id = new ObjectId()
  30. this.doc = { _id: (this.doc_id = 'test-doc-id') }
  31. this.docName = 'doc.tex'
  32. this.docLines = ['1234', 'dskl']
  33. this.file = { _id: (this.file_id = 'dasdkjk') }
  34. this.fileName = 'file.png'
  35. this.fsPath = '/folder/file.png'
  36. this.linkedFileData = { provider: 'url' }
  37. this.newFile = { _id: 'new-file-id' }
  38. this.folder_id = '123ksajdn'
  39. this.folder = { _id: this.folder_id }
  40. this.folderName = 'folder'
  41. this.callback = sinon.stub()
  42. return (this.EditorController = SandboxedModule.require(modulePath, {
  43. requires: {
  44. '../Project/ProjectEntityUpdateHandler':
  45. (this.ProjectEntityUpdateHandler = {}),
  46. '../Project/ProjectOptionsHandler': (this.ProjectOptionsHandler = {
  47. setCompiler: sinon.stub().yields(),
  48. setImageName: sinon.stub().yields(),
  49. setSpellCheckLanguage: sinon.stub().yields(),
  50. }),
  51. '../Project/ProjectDetailsHandler': (this.ProjectDetailsHandler = {
  52. setProjectDescription: sinon.stub().yields(),
  53. renameProject: sinon.stub().yields(),
  54. setPublicAccessLevel: sinon.stub().yields(),
  55. }),
  56. '../Project/ProjectDeleter': (this.ProjectDeleter = {}),
  57. '../DocumentUpdater/DocumentUpdaterHandler':
  58. (this.DocumentUpdaterHandler = {
  59. flushDocToMongo: sinon.stub().yields(),
  60. setDocument: sinon.stub().yields(),
  61. }),
  62. './EditorRealTimeController': (this.EditorRealTimeController = {
  63. emitToRoom: sinon.stub(),
  64. }),
  65. '@overleaf/metrics': (this.Metrics = { inc: sinon.stub() }),
  66. },
  67. }))
  68. })
  69. describe('addDoc', function () {
  70. beforeEach(function () {
  71. this.ProjectEntityUpdateHandler.addDocWithRanges = sinon
  72. .stub()
  73. .yields(null, this.doc, this.folder_id)
  74. return this.EditorController.addDoc(
  75. this.project_id,
  76. this.folder_id,
  77. this.docName,
  78. this.docLines,
  79. this.source,
  80. this.user_id,
  81. this.callback
  82. )
  83. })
  84. it('should add the doc using the project entity handler', function () {
  85. return this.ProjectEntityUpdateHandler.addDocWithRanges
  86. .calledWith(
  87. this.project_id,
  88. this.folder_id,
  89. this.docName,
  90. this.docLines,
  91. {},
  92. this.user_id,
  93. this.source
  94. )
  95. .should.equal(true)
  96. })
  97. it('should send the update out to the users in the project', function () {
  98. return this.EditorRealTimeController.emitToRoom
  99. .calledWith(
  100. this.project_id,
  101. 'reciveNewDoc',
  102. this.folder_id,
  103. this.doc,
  104. this.source,
  105. this.user_id
  106. )
  107. .should.equal(true)
  108. })
  109. it('calls the callback', function () {
  110. return this.callback.calledWith(null, this.doc).should.equal(true)
  111. })
  112. })
  113. describe('addFile', function () {
  114. beforeEach(function () {
  115. this.ProjectEntityUpdateHandler.addFile = sinon
  116. .stub()
  117. .yields(null, this.file, this.folder_id)
  118. return this.EditorController.addFile(
  119. this.project_id,
  120. this.folder_id,
  121. this.fileName,
  122. this.fsPath,
  123. this.linkedFileData,
  124. this.source,
  125. this.user_id,
  126. this.callback
  127. )
  128. })
  129. it('should add the folder using the project entity handler', function () {
  130. return this.ProjectEntityUpdateHandler.addFile
  131. .calledWith(
  132. this.project_id,
  133. this.folder_id,
  134. this.fileName,
  135. this.fsPath,
  136. this.linkedFileData,
  137. this.user_id,
  138. this.source
  139. )
  140. .should.equal(true)
  141. })
  142. it('should send the update of a new folder out to the users in the project', function () {
  143. return this.EditorRealTimeController.emitToRoom
  144. .calledWith(
  145. this.project_id,
  146. 'reciveNewFile',
  147. this.folder_id,
  148. this.file,
  149. this.source,
  150. this.linkedFileData,
  151. this.user_id
  152. )
  153. .should.equal(true)
  154. })
  155. it('calls the callback', function () {
  156. return this.callback.calledWith(null, this.file).should.equal(true)
  157. })
  158. })
  159. describe('upsertDoc', function () {
  160. beforeEach(function () {
  161. this.ProjectEntityUpdateHandler.upsertDoc = sinon
  162. .stub()
  163. .yields(null, this.doc, false)
  164. return this.EditorController.upsertDoc(
  165. this.project_id,
  166. this.folder_id,
  167. this.docName,
  168. this.docLines,
  169. this.source,
  170. this.user_id,
  171. this.callback
  172. )
  173. })
  174. it('upserts the doc using the project entity handler', function () {
  175. return this.ProjectEntityUpdateHandler.upsertDoc
  176. .calledWith(
  177. this.project_id,
  178. this.folder_id,
  179. this.docName,
  180. this.docLines,
  181. this.source
  182. )
  183. .should.equal(true)
  184. })
  185. it('returns the doc', function () {
  186. return this.callback.calledWith(null, this.doc).should.equal(true)
  187. })
  188. describe('doc does not exist', function () {
  189. beforeEach(function () {
  190. this.ProjectEntityUpdateHandler.upsertDoc = sinon
  191. .stub()
  192. .yields(null, this.doc, true)
  193. return this.EditorController.upsertDoc(
  194. this.project_id,
  195. this.folder_id,
  196. this.docName,
  197. this.docLines,
  198. this.source,
  199. this.user_id,
  200. this.callback
  201. )
  202. })
  203. it('sends an update out to users in the project', function () {
  204. return this.EditorRealTimeController.emitToRoom
  205. .calledWith(
  206. this.project_id,
  207. 'reciveNewDoc',
  208. this.folder_id,
  209. this.doc,
  210. this.source,
  211. this.user_id
  212. )
  213. .should.equal(true)
  214. })
  215. })
  216. })
  217. describe('upsertFile', function () {
  218. beforeEach(function () {
  219. this.ProjectEntityUpdateHandler.upsertFile = sinon
  220. .stub()
  221. .yields(null, this.newFile, false, this.file)
  222. return this.EditorController.upsertFile(
  223. this.project_id,
  224. this.folder_id,
  225. this.fileName,
  226. this.fsPath,
  227. this.linkedFileData,
  228. this.source,
  229. this.user_id,
  230. this.callback
  231. )
  232. })
  233. it('upserts the file using the project entity handler', function () {
  234. return this.ProjectEntityUpdateHandler.upsertFile
  235. .calledWith(
  236. this.project_id,
  237. this.folder_id,
  238. this.fileName,
  239. this.fsPath,
  240. this.linkedFileData,
  241. this.user_id,
  242. this.source
  243. )
  244. .should.equal(true)
  245. })
  246. it('returns the file', function () {
  247. return this.callback.calledWith(null, this.newFile).should.equal(true)
  248. })
  249. describe('file does not exist', function () {
  250. beforeEach(function () {
  251. this.ProjectEntityUpdateHandler.upsertFile = sinon
  252. .stub()
  253. .yields(null, this.file, true)
  254. return this.EditorController.upsertFile(
  255. this.project_id,
  256. this.folder_id,
  257. this.fileName,
  258. this.fsPath,
  259. this.linkedFileData,
  260. this.source,
  261. this.user_id,
  262. this.callback
  263. )
  264. })
  265. it('should send the update out to users in the project', function () {
  266. return this.EditorRealTimeController.emitToRoom
  267. .calledWith(
  268. this.project_id,
  269. 'reciveNewFile',
  270. this.folder_id,
  271. this.file,
  272. this.source,
  273. this.linkedFileData,
  274. this.user_id
  275. )
  276. .should.equal(true)
  277. })
  278. })
  279. })
  280. describe('upsertDocWithPath', function () {
  281. beforeEach(function () {
  282. this.docPath = '/folder/doc'
  283. this.ProjectEntityUpdateHandler.upsertDocWithPath = sinon
  284. .stub()
  285. .yields(null, this.doc, false, [], this.folder)
  286. return this.EditorController.upsertDocWithPath(
  287. this.project_id,
  288. this.docPath,
  289. this.docLines,
  290. this.source,
  291. this.user_id,
  292. this.callback
  293. )
  294. })
  295. it('upserts the doc using the project entity handler', function () {
  296. return this.ProjectEntityUpdateHandler.upsertDocWithPath
  297. .calledWith(this.project_id, this.docPath, this.docLines, this.source)
  298. .should.equal(true)
  299. })
  300. describe('doc does not exist', function () {
  301. beforeEach(function () {
  302. this.ProjectEntityUpdateHandler.upsertDocWithPath = sinon
  303. .stub()
  304. .yields(null, this.doc, true, [], this.folder)
  305. return this.EditorController.upsertDocWithPath(
  306. this.project_id,
  307. this.docPath,
  308. this.docLines,
  309. this.source,
  310. this.user_id,
  311. this.callback
  312. )
  313. })
  314. it('should send the update for the doc out to users in the project', function () {
  315. return this.EditorRealTimeController.emitToRoom
  316. .calledWith(
  317. this.project_id,
  318. 'reciveNewDoc',
  319. this.folder_id,
  320. this.doc,
  321. this.source,
  322. this.user_id
  323. )
  324. .should.equal(true)
  325. })
  326. })
  327. describe('folders required for doc do not exist', function () {
  328. beforeEach(function () {
  329. const folders = [
  330. (this.folderA = { _id: 2, parentFolder_id: 1 }),
  331. (this.folderB = { _id: 3, parentFolder_id: 2 }),
  332. ]
  333. this.ProjectEntityUpdateHandler.upsertDocWithPath = sinon
  334. .stub()
  335. .yields(null, this.doc, true, folders, this.folderB)
  336. return this.EditorController.upsertDocWithPath(
  337. this.project_id,
  338. this.docPath,
  339. this.docLines,
  340. this.source,
  341. this.user_id,
  342. this.callback
  343. )
  344. })
  345. it('should send the update for each folder to users in the project', function () {
  346. this.EditorRealTimeController.emitToRoom
  347. .calledWith(
  348. this.project_id,
  349. 'reciveNewFolder',
  350. this.folderA.parentFolder_id,
  351. this.folderA
  352. )
  353. .should.equal(true)
  354. return this.EditorRealTimeController.emitToRoom
  355. .calledWith(
  356. this.project_id,
  357. 'reciveNewFolder',
  358. this.folderB.parentFolder_id,
  359. this.folderB
  360. )
  361. .should.equal(true)
  362. })
  363. })
  364. })
  365. describe('upsertFileWithPath', function () {
  366. beforeEach(function () {
  367. this.filePath = '/folder/file'
  368. this.ProjectEntityUpdateHandler.upsertFileWithPath = sinon
  369. .stub()
  370. .yields(null, this.newFile, false, this.file, [], this.folder)
  371. return this.EditorController.upsertFileWithPath(
  372. this.project_id,
  373. this.filePath,
  374. this.fsPath,
  375. this.linkedFileData,
  376. this.source,
  377. this.user_id,
  378. this.callback
  379. )
  380. })
  381. it('upserts the file using the project entity handler', function () {
  382. return this.ProjectEntityUpdateHandler.upsertFileWithPath
  383. .calledWith(
  384. this.project_id,
  385. this.filePath,
  386. this.fsPath,
  387. this.linkedFileData,
  388. this.user_id,
  389. this.source
  390. )
  391. .should.equal(true)
  392. })
  393. describe('file does not exist', function () {
  394. beforeEach(function () {
  395. this.ProjectEntityUpdateHandler.upsertFileWithPath = sinon
  396. .stub()
  397. .yields(null, this.file, true, undefined, [], this.folder)
  398. return this.EditorController.upsertFileWithPath(
  399. this.project_id,
  400. this.filePath,
  401. this.fsPath,
  402. this.linkedFileData,
  403. this.source,
  404. this.user_id,
  405. this.callback
  406. )
  407. })
  408. it('should send the update for the file out to users in the project', function () {
  409. return this.EditorRealTimeController.emitToRoom
  410. .calledWith(
  411. this.project_id,
  412. 'reciveNewFile',
  413. this.folder_id,
  414. this.file,
  415. this.source,
  416. this.linkedFileData,
  417. this.user_id
  418. )
  419. .should.equal(true)
  420. })
  421. })
  422. describe('folders required for file do not exist', function () {
  423. beforeEach(function () {
  424. const folders = [
  425. (this.folderA = { _id: 2, parentFolder_id: 1 }),
  426. (this.folderB = { _id: 3, parentFolder_id: 2 }),
  427. ]
  428. this.ProjectEntityUpdateHandler.upsertFileWithPath = sinon
  429. .stub()
  430. .yields(null, this.file, true, undefined, folders, this.folderB)
  431. return this.EditorController.upsertFileWithPath(
  432. this.project_id,
  433. this.filePath,
  434. this.fsPath,
  435. this.linkedFileData,
  436. this.source,
  437. this.user_id,
  438. this.callback
  439. )
  440. })
  441. it('should send the update for each folder to users in the project', function () {
  442. this.EditorRealTimeController.emitToRoom
  443. .calledWith(
  444. this.project_id,
  445. 'reciveNewFolder',
  446. this.folderA.parentFolder_id,
  447. this.folderA
  448. )
  449. .should.equal(true)
  450. return this.EditorRealTimeController.emitToRoom
  451. .calledWith(
  452. this.project_id,
  453. 'reciveNewFolder',
  454. this.folderB.parentFolder_id,
  455. this.folderB
  456. )
  457. .should.equal(true)
  458. })
  459. })
  460. })
  461. describe('addFolder', function () {
  462. beforeEach(function () {
  463. this.EditorController._notifyProjectUsersOfNewFolder = sinon
  464. .stub()
  465. .yields()
  466. this.ProjectEntityUpdateHandler.addFolder = sinon
  467. .stub()
  468. .yields(null, this.folder, this.folder_id)
  469. return this.EditorController.addFolder(
  470. this.project_id,
  471. this.folder_id,
  472. this.folderName,
  473. this.source,
  474. this.user_id,
  475. this.callback
  476. )
  477. })
  478. it('should add the folder using the project entity handler', function () {
  479. return this.ProjectEntityUpdateHandler.addFolder
  480. .calledWith(
  481. this.project_id,
  482. this.folder_id,
  483. this.folderName,
  484. this.user_id
  485. )
  486. .should.equal(true)
  487. })
  488. it('should notifyProjectUsersOfNewFolder', function () {
  489. return this.EditorController._notifyProjectUsersOfNewFolder
  490. .calledWith(this.project_id, this.folder_id, this.folder, this.user_id)
  491. .should.equal(true)
  492. })
  493. it('should return the folder in the callback', function () {
  494. return this.callback.calledWith(null, this.folder).should.equal(true)
  495. })
  496. })
  497. describe('mkdirp', function () {
  498. beforeEach(function () {
  499. this.path = 'folder1/folder2'
  500. this.folders = [
  501. (this.folderA = { _id: 2, parentFolder_id: 1 }),
  502. (this.folderB = { _id: 3, parentFolder_id: 2 }),
  503. ]
  504. this.userId = new ObjectId().toString()
  505. this.EditorController._notifyProjectUsersOfNewFolders = sinon
  506. .stub()
  507. .yields()
  508. this.ProjectEntityUpdateHandler.mkdirp = sinon
  509. .stub()
  510. .yields(null, this.folders, this.folder)
  511. return this.EditorController.mkdirp(
  512. this.project_id,
  513. this.path,
  514. this.userId,
  515. this.callback
  516. )
  517. })
  518. it('should create the folder using the project entity handler', function () {
  519. return this.ProjectEntityUpdateHandler.mkdirp
  520. .calledWith(this.project_id, this.path, this.userId)
  521. .should.equal(true)
  522. })
  523. it('should notifyProjectUsersOfNewFolder', function () {
  524. return this.EditorController._notifyProjectUsersOfNewFolders.calledWith(
  525. this.project_id,
  526. this.folders
  527. )
  528. })
  529. it('should return the folder in the callback', function () {
  530. return this.callback
  531. .calledWith(null, this.folders, this.folder)
  532. .should.equal(true)
  533. })
  534. })
  535. describe('deleteEntity', function () {
  536. beforeEach(function () {
  537. this.entity_id = 'entity_id_here'
  538. this.type = 'doc'
  539. this.ProjectEntityUpdateHandler.deleteEntity = sinon.stub().yields()
  540. return this.EditorController.deleteEntity(
  541. this.project_id,
  542. this.entity_id,
  543. this.type,
  544. this.source,
  545. this.user_id,
  546. this.callback
  547. )
  548. })
  549. it('should delete the folder using the project entity handler', function () {
  550. return this.ProjectEntityUpdateHandler.deleteEntity
  551. .calledWith(
  552. this.project_id,
  553. this.entity_id,
  554. this.type,
  555. this.user_id,
  556. this.source
  557. )
  558. .should.equal(true)
  559. })
  560. it('notify users an entity has been deleted', function () {
  561. return this.EditorRealTimeController.emitToRoom
  562. .calledWith(
  563. this.project_id,
  564. 'removeEntity',
  565. this.entity_id,
  566. this.source
  567. )
  568. .should.equal(true)
  569. })
  570. })
  571. describe('deleteEntityWithPath', function () {
  572. beforeEach(function () {
  573. this.entity_id = 'entity_id_here'
  574. this.ProjectEntityUpdateHandler.deleteEntityWithPath = sinon
  575. .stub()
  576. .yields(null, this.entity_id)
  577. this.path = 'folder1/folder2'
  578. return this.EditorController.deleteEntityWithPath(
  579. this.project_id,
  580. this.path,
  581. this.source,
  582. this.user_id,
  583. this.callback
  584. )
  585. })
  586. it('should delete the folder using the project entity handler', function () {
  587. return this.ProjectEntityUpdateHandler.deleteEntityWithPath
  588. .calledWith(this.project_id, this.path, this.user_id, this.source)
  589. .should.equal(true)
  590. })
  591. it('notify users an entity has been deleted', function () {
  592. return this.EditorRealTimeController.emitToRoom
  593. .calledWith(
  594. this.project_id,
  595. 'removeEntity',
  596. this.entity_id,
  597. this.source
  598. )
  599. .should.equal(true)
  600. })
  601. })
  602. describe('updateProjectDescription', function () {
  603. beforeEach(function () {
  604. this.description = 'new description'
  605. return this.EditorController.updateProjectDescription(
  606. this.project_id,
  607. this.description,
  608. this.callback
  609. )
  610. })
  611. it('should send the new description to the project details handler', function () {
  612. return this.ProjectDetailsHandler.setProjectDescription
  613. .calledWith(this.project_id, this.description)
  614. .should.equal(true)
  615. })
  616. it('should notify the other clients about the updated description', function () {
  617. return this.EditorRealTimeController.emitToRoom
  618. .calledWith(
  619. this.project_id,
  620. 'projectDescriptionUpdated',
  621. this.description
  622. )
  623. .should.equal(true)
  624. })
  625. })
  626. describe('deleteProject', function () {
  627. beforeEach(function () {
  628. this.err = 'errro'
  629. return (this.ProjectDeleter.deleteProject = sinon
  630. .stub()
  631. .callsArgWith(1, this.err))
  632. })
  633. it('should call the project handler', function (done) {
  634. return this.EditorController.deleteProject(this.project_id, err => {
  635. err.should.equal(this.err)
  636. this.ProjectDeleter.deleteProject
  637. .calledWith(this.project_id)
  638. .should.equal(true)
  639. return done()
  640. })
  641. })
  642. })
  643. describe('renameEntity', function () {
  644. beforeEach(function (done) {
  645. this.entity_id = 'entity_id_here'
  646. this.entityType = 'doc'
  647. this.newName = 'bobsfile.tex'
  648. this.ProjectEntityUpdateHandler.renameEntity = sinon.stub().yields()
  649. return this.EditorController.renameEntity(
  650. this.project_id,
  651. this.entity_id,
  652. this.entityType,
  653. this.newName,
  654. this.user_id,
  655. this.source,
  656. done
  657. )
  658. })
  659. it('should call the project handler', function () {
  660. return this.ProjectEntityUpdateHandler.renameEntity
  661. .calledWith(
  662. this.project_id,
  663. this.entity_id,
  664. this.entityType,
  665. this.newName,
  666. this.user_id,
  667. this.source
  668. )
  669. .should.equal(true)
  670. })
  671. it('should emit the update to the room', function () {
  672. return this.EditorRealTimeController.emitToRoom
  673. .calledWith(
  674. this.project_id,
  675. 'reciveEntityRename',
  676. this.entity_id,
  677. this.newName
  678. )
  679. .should.equal(true)
  680. })
  681. })
  682. describe('moveEntity', function () {
  683. beforeEach(function () {
  684. this.entity_id = 'entity_id_here'
  685. this.entityType = 'doc'
  686. this.ProjectEntityUpdateHandler.moveEntity = sinon.stub().yields()
  687. return this.EditorController.moveEntity(
  688. this.project_id,
  689. this.entity_id,
  690. this.folder_id,
  691. this.entityType,
  692. this.user_id,
  693. this.source,
  694. this.callback
  695. )
  696. })
  697. it('should call the ProjectEntityUpdateHandler', function () {
  698. return this.ProjectEntityUpdateHandler.moveEntity
  699. .calledWith(
  700. this.project_id,
  701. this.entity_id,
  702. this.folder_id,
  703. this.entityType,
  704. this.user_id,
  705. this.source
  706. )
  707. .should.equal(true)
  708. })
  709. it('should emit the update to the room', function () {
  710. return this.EditorRealTimeController.emitToRoom
  711. .calledWith(
  712. this.project_id,
  713. 'reciveEntityMove',
  714. this.entity_id,
  715. this.folder_id
  716. )
  717. .should.equal(true)
  718. })
  719. it('calls the callback', function () {
  720. return this.callback.called.should.equal(true)
  721. })
  722. })
  723. describe('renameProject', function () {
  724. beforeEach(function () {
  725. this.err = 'errro'
  726. this.newName = 'new name here'
  727. return this.EditorController.renameProject(
  728. this.project_id,
  729. this.newName,
  730. this.callback
  731. )
  732. })
  733. it('should call the EditorController', function () {
  734. return this.ProjectDetailsHandler.renameProject
  735. .calledWith(this.project_id, this.newName)
  736. .should.equal(true)
  737. })
  738. it('should emit the update to the room', function () {
  739. return this.EditorRealTimeController.emitToRoom
  740. .calledWith(this.project_id, 'projectNameUpdated', this.newName)
  741. .should.equal(true)
  742. })
  743. })
  744. describe('setCompiler', function () {
  745. beforeEach(function () {
  746. this.compiler = 'latex'
  747. return this.EditorController.setCompiler(
  748. this.project_id,
  749. this.compiler,
  750. this.callback
  751. )
  752. })
  753. it('should send the new compiler and project id to the project options handler', function () {
  754. this.ProjectOptionsHandler.setCompiler
  755. .calledWith(this.project_id, this.compiler)
  756. .should.equal(true)
  757. return this.EditorRealTimeController.emitToRoom
  758. .calledWith(this.project_id, 'compilerUpdated', this.compiler)
  759. .should.equal(true)
  760. })
  761. })
  762. describe('setImageName', function () {
  763. beforeEach(function () {
  764. this.imageName = 'texlive-1234.5'
  765. return this.EditorController.setImageName(
  766. this.project_id,
  767. this.imageName,
  768. this.callback
  769. )
  770. })
  771. it('should send the new imageName and project id to the project options handler', function () {
  772. this.ProjectOptionsHandler.setImageName
  773. .calledWith(this.project_id, this.imageName)
  774. .should.equal(true)
  775. return this.EditorRealTimeController.emitToRoom
  776. .calledWith(this.project_id, 'imageNameUpdated', this.imageName)
  777. .should.equal(true)
  778. })
  779. })
  780. describe('setSpellCheckLanguage', function () {
  781. beforeEach(function () {
  782. this.languageCode = 'fr'
  783. return this.EditorController.setSpellCheckLanguage(
  784. this.project_id,
  785. this.languageCode,
  786. this.callback
  787. )
  788. })
  789. it('should send the new languageCode and project id to the project options handler', function () {
  790. this.ProjectOptionsHandler.setSpellCheckLanguage
  791. .calledWith(this.project_id, this.languageCode)
  792. .should.equal(true)
  793. return this.EditorRealTimeController.emitToRoom
  794. .calledWith(
  795. this.project_id,
  796. 'spellCheckLanguageUpdated',
  797. this.languageCode
  798. )
  799. .should.equal(true)
  800. })
  801. })
  802. describe('setPublicAccessLevel', function () {
  803. describe('when setting to private', function () {
  804. beforeEach(function () {
  805. this.newAccessLevel = 'private'
  806. this.ProjectDetailsHandler.ensureTokensArePresent = sinon
  807. .stub()
  808. .yields()
  809. return this.EditorController.setPublicAccessLevel(
  810. this.project_id,
  811. this.newAccessLevel,
  812. this.callback
  813. )
  814. })
  815. it('should set the access level', function () {
  816. return this.ProjectDetailsHandler.setPublicAccessLevel
  817. .calledWith(this.project_id, this.newAccessLevel)
  818. .should.equal(true)
  819. })
  820. it('should broadcast the access level change', function () {
  821. return this.EditorRealTimeController.emitToRoom
  822. .calledWith(this.project_id, 'project:publicAccessLevel:changed')
  823. .should.equal(true)
  824. })
  825. it('should not ensure tokens are present for project', function () {
  826. return this.ProjectDetailsHandler.ensureTokensArePresent
  827. .calledWith(this.project_id)
  828. .should.equal(false)
  829. })
  830. })
  831. describe('when setting to tokenBased', function () {
  832. beforeEach(function () {
  833. this.newAccessLevel = 'tokenBased'
  834. this.tokens = { readOnly: 'aaa', readAndWrite: '42bbb' }
  835. this.ProjectDetailsHandler.ensureTokensArePresent = sinon
  836. .stub()
  837. .yields()
  838. return this.EditorController.setPublicAccessLevel(
  839. this.project_id,
  840. this.newAccessLevel,
  841. this.callback
  842. )
  843. })
  844. it('should set the access level', function () {
  845. return this.ProjectDetailsHandler.setPublicAccessLevel
  846. .calledWith(this.project_id, this.newAccessLevel)
  847. .should.equal(true)
  848. })
  849. it('should broadcast the access level change', function () {
  850. return this.EditorRealTimeController.emitToRoom
  851. .calledWith(this.project_id, 'project:publicAccessLevel:changed')
  852. .should.equal(true)
  853. })
  854. it('should ensure tokens are present for project', function () {
  855. return this.ProjectDetailsHandler.ensureTokensArePresent
  856. .calledWith(this.project_id)
  857. .should.equal(true)
  858. })
  859. })
  860. })
  861. describe('setRootDoc', function () {
  862. beforeEach(function () {
  863. this.newRootDocID = '21312321321'
  864. this.ProjectEntityUpdateHandler.setRootDoc = sinon.stub().yields()
  865. return this.EditorController.setRootDoc(
  866. this.project_id,
  867. this.newRootDocID,
  868. this.callback
  869. )
  870. })
  871. it('should call the ProjectEntityUpdateHandler', function () {
  872. return this.ProjectEntityUpdateHandler.setRootDoc
  873. .calledWith(this.project_id, this.newRootDocID)
  874. .should.equal(true)
  875. })
  876. it('should emit the update to the room', function () {
  877. return this.EditorRealTimeController.emitToRoom
  878. .calledWith(this.project_id, 'rootDocUpdated', this.newRootDocID)
  879. .should.equal(true)
  880. })
  881. })
  882. describe('setMainBibliographyDoc', function () {
  883. describe('on success', function () {
  884. beforeEach(function (done) {
  885. this.mainBibliographyId = 'bib-doc-id'
  886. this.ProjectEntityUpdateHandler.setMainBibliographyDoc = sinon
  887. .stub()
  888. .yields()
  889. this.callback = sinon.stub().callsFake(done)
  890. this.EditorController.setMainBibliographyDoc(
  891. this.project_id,
  892. this.mainBibliographyId,
  893. this.callback
  894. )
  895. })
  896. it('should forward the call to the ProjectEntityUpdateHandler', function () {
  897. expect(
  898. this.ProjectEntityUpdateHandler.setMainBibliographyDoc
  899. ).to.have.been.calledWith(this.project_id, this.mainBibliographyId)
  900. })
  901. it('should emit the update to the room', function () {
  902. expect(
  903. this.EditorRealTimeController.emitToRoom
  904. ).to.have.been.calledWith(
  905. this.project_id,
  906. 'mainBibliographyDocUpdated',
  907. this.mainBibliographyId
  908. )
  909. })
  910. it('should return nothing', function () {
  911. expect(this.callback).to.have.been.calledWithExactly()
  912. })
  913. })
  914. describe('on error', function () {
  915. beforeEach(function (done) {
  916. this.mainBibliographyId = 'bib-doc-id'
  917. this.error = new Error('oh no')
  918. this.ProjectEntityUpdateHandler.setMainBibliographyDoc = sinon
  919. .stub()
  920. .yields(this.error)
  921. this.callback = sinon.stub().callsFake(() => done())
  922. this.EditorController.setMainBibliographyDoc(
  923. this.project_id,
  924. this.mainBibliographyId,
  925. this.callback
  926. )
  927. })
  928. it('should forward the call to the ProjectEntityUpdateHandler', function () {
  929. expect(
  930. this.ProjectEntityUpdateHandler.setMainBibliographyDoc
  931. ).to.have.been.calledWith(this.project_id, this.mainBibliographyId)
  932. })
  933. it('should return the error', function () {
  934. expect(this.callback).to.have.been.calledWithExactly(this.error)
  935. })
  936. it('should not emit the update to the room', function () {
  937. expect(this.EditorRealTimeController.emitToRoom).to.not.have.been.called
  938. })
  939. })
  940. })
  941. describe('appendToDoc', function () {
  942. describe('on success', function () {
  943. beforeEach(function () {
  944. this.docId = 'doc-1'
  945. this.ProjectEntityUpdateHandler.appendToDoc = sinon
  946. .stub()
  947. .yields(null, { rev: '1' })
  948. this.EditorController.appendToDoc(
  949. this.project_id,
  950. this.docId,
  951. this.docLines,
  952. this.source,
  953. this.user_id,
  954. this.callback
  955. )
  956. })
  957. it('appends to the doc using the project entity handler', function () {
  958. this.ProjectEntityUpdateHandler.appendToDoc
  959. .calledWith(this.project_id, this.docId, this.docLines, this.source)
  960. .should.equal(true)
  961. })
  962. })
  963. describe('on error', function () {
  964. beforeEach(function () {
  965. this.docId = 'doc-1'
  966. this.ProjectEntityUpdateHandler.appendToDoc = sinon
  967. .stub()
  968. .yields(new Error('foo'))
  969. this.EditorController.appendToDoc(
  970. this.project_id,
  971. this.docId,
  972. this.docLines,
  973. this.source,
  974. this.user_id,
  975. this.callback
  976. )
  977. })
  978. it('tries to append to the doc using the project entity handler', function () {
  979. this.ProjectEntityUpdateHandler.appendToDoc
  980. .calledWith(this.project_id, this.docId, this.docLines, this.source)
  981. .should.equal(true)
  982. })
  983. it('tags the error', function () {
  984. this.callback.calledWith(sinon.match.instanceOf(OError))
  985. })
  986. })
  987. })
  988. })