HistoryV2ManagerTests.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649
  1. /* eslint-disable
  2. max-len,
  3. no-return-assign,
  4. */
  5. /* global inject, sinon */
  6. // TODO: This file was created by bulk-decaffeinate.
  7. // Fix any style issues and re-enable lint.
  8. /*
  9. * decaffeinate suggestions:
  10. * DS102: Remove unnecessary code created because of implicit returns
  11. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  12. */
  13. import HistoryV2Manager from '../../../../frontend/js/ide/history/HistoryV2Manager'
  14. export default describe('HistoryV2Manager', function () {
  15. beforeEach(function (done) {
  16. this.defaultHistoryScope = {
  17. isV2: true,
  18. updates: [],
  19. viewMode: 'point_in_time',
  20. nextBeforeTimestamp: null,
  21. loading: false,
  22. atEnd: false,
  23. userHasFullFeature: undefined,
  24. freeHistoryLimitHit: false,
  25. selection: {
  26. docs: {},
  27. pathname: null,
  28. range: {
  29. fromV: null,
  30. toV: null,
  31. },
  32. hoveredRange: {
  33. fromV: null,
  34. toV: null,
  35. },
  36. diff: null,
  37. files: [],
  38. file: null,
  39. },
  40. error: null,
  41. showOnlyLabels: false,
  42. labels: null,
  43. loadingFileTree: true,
  44. }
  45. this.sampleUpdates = [
  46. {
  47. fromV: 4,
  48. toV: 5,
  49. meta: {
  50. users: [
  51. {
  52. first_name: 'john.doe',
  53. last_name: '',
  54. email: 'john.doe@domain.tld',
  55. id: '5b57299087712202fb599ab4',
  56. hue: 200,
  57. },
  58. ],
  59. start_ts: 1544021278346,
  60. end_ts: 1544021278346,
  61. },
  62. labels: [
  63. {
  64. id: '5c07e822042e67003b448f18',
  65. comment: 'My first label',
  66. version: 5,
  67. user_id: '5b57299087712202fb599ab4',
  68. created_at: '2018-12-05T15:00:50.688Z',
  69. },
  70. ],
  71. pathnames: [],
  72. project_ops: [
  73. {
  74. add: {
  75. pathname: 'chapters/chapter1.tex',
  76. },
  77. atV: 4,
  78. },
  79. ],
  80. },
  81. {
  82. fromV: 3,
  83. toV: 4,
  84. meta: {
  85. users: [
  86. {
  87. first_name: 'john.doe',
  88. last_name: '',
  89. email: 'john.doe@domain.tld',
  90. id: '5b57299087712202fb599ab4',
  91. hue: 200,
  92. },
  93. ],
  94. start_ts: 1544021262622,
  95. end_ts: 1544021262622,
  96. },
  97. labels: [],
  98. pathnames: ['main.tex'],
  99. project_ops: [],
  100. },
  101. {
  102. fromV: 0,
  103. toV: 3,
  104. meta: {
  105. users: [
  106. {
  107. first_name: 'john.doe',
  108. last_name: '',
  109. email: 'john.doe@domain.tld',
  110. id: '5b57299087712202fb599ab4',
  111. hue: 200,
  112. },
  113. ],
  114. start_ts: 1544021213540,
  115. end_ts: 1544021213618,
  116. },
  117. labels: [],
  118. pathnames: [],
  119. project_ops: [
  120. {
  121. add: {
  122. pathname: 'universe.jpg',
  123. },
  124. atV: 2,
  125. },
  126. {
  127. add: {
  128. pathname: 'references.bib',
  129. },
  130. atV: 1,
  131. },
  132. {
  133. add: {
  134. pathname: 'main.tex',
  135. },
  136. atV: 0,
  137. },
  138. ],
  139. },
  140. ]
  141. inject(($q, $http, $filter, $rootScope) => {
  142. this.$scope = $rootScope.$new()
  143. this.$scope.project = {
  144. features: {
  145. versioning: true,
  146. },
  147. }
  148. this.$scope.user = {
  149. isAdmin: false,
  150. }
  151. this.ide = {
  152. globalEditorWatchdogManager: { attachToEditor() {} },
  153. $q,
  154. $http,
  155. $filter,
  156. }
  157. this.eventTracking = {
  158. sendMB: () => {},
  159. }
  160. this.localStorage = sinon.stub().returns(null)
  161. this.historyManager = new HistoryV2Manager(
  162. this.ide,
  163. this.$scope,
  164. this.localStorage,
  165. this.eventTracking
  166. )
  167. done()
  168. })
  169. })
  170. it('should setup the history scope on initialization', function () {
  171. expect(this.$scope.history).to.deep.equal(this.defaultHistoryScope)
  172. })
  173. it('should keep history updates after performing a soft reset', function () {
  174. const historyScopeWithUpdates = Object.assign(
  175. {},
  176. this.defaultHistoryScope,
  177. {
  178. updates: this.sampleUpdates,
  179. }
  180. )
  181. this.$scope.history.updates = this.sampleUpdates
  182. this.historyManager.softReset()
  183. expect(this.$scope.history).to.deep.equal(historyScopeWithUpdates)
  184. })
  185. it('should discard history updates after performing a hard reset', function () {
  186. this.$scope.history.updates = this.sampleUpdates
  187. this.historyManager.hardReset()
  188. expect(this.$scope.history).to.deep.equal(this.defaultHistoryScope)
  189. })
  190. it('should setup history with full access to the feature if the project has versioning', function () {
  191. this.$scope.$digest()
  192. expect(this.$scope.history.userHasFullFeature).to.equal(true)
  193. })
  194. it('should setup history without full access to the feature if the project does not have versioning', function () {
  195. this.$scope.project.features.versioning = false
  196. this.historyManager = new HistoryV2Manager(
  197. this.ide,
  198. this.$scope,
  199. this.localStorage
  200. )
  201. this.$scope.$digest()
  202. expect(this.$scope.history.userHasFullFeature).to.equal(false)
  203. })
  204. it('should setup history with full access to the feature for admin users even if the project does not have versioning', function () {
  205. this.$scope.project.features.versioning = false
  206. this.$scope.user.isAdmin = true
  207. this.historyManager = new HistoryV2Manager(
  208. this.ide,
  209. this.$scope,
  210. this.localStorage
  211. )
  212. this.$scope.$digest()
  213. expect(this.$scope.history.userHasFullFeature).to.equal(true)
  214. })
  215. describe('autoSelectFile', function () {
  216. describe('for compare mode', function () {
  217. beforeEach(function () {
  218. this.mockedFilesList = [
  219. {
  220. pathname: 'main.tex',
  221. },
  222. {
  223. pathname: 'references.bib',
  224. },
  225. {
  226. pathname: 'universe.jpg',
  227. },
  228. {
  229. pathname: 'chapters/chapter2.tex',
  230. },
  231. {
  232. pathname: 'chapters/draft.tex',
  233. operation: 'removed',
  234. deletedAtV: 47,
  235. },
  236. {
  237. pathname: 'chapters/chapter3.tex',
  238. operation: 'added',
  239. },
  240. {
  241. pathname: 'chapters/chapter1.tex',
  242. operation: 'edited',
  243. },
  244. {
  245. pathname: 'chapters/foo.tex',
  246. oldPathname: 'chapters/bar.tex',
  247. operation: 'renamed',
  248. },
  249. ]
  250. this.mockedMainTex = this.mockedFilesList[0]
  251. this.mockedReferencesFile = this.mockedFilesList[1]
  252. this.mockedRemovedFile = this.mockedFilesList[4]
  253. this.mockedAddedFile = this.mockedFilesList[5]
  254. this.mockedEditedFile = this.mockedFilesList[6]
  255. this.mockedRenamedFile = this.mockedFilesList[7]
  256. this.$scope.history.viewMode = 'compare'
  257. this.$scope.history.selection.files = this.mockedFilesList
  258. })
  259. describe('with a previously selected file', function () {
  260. it('should prefer the previously selected file if it is available and has operations', function () {
  261. this.historyManager._previouslySelectedPathname =
  262. this.mockedAddedFile.pathname
  263. this.historyManager.autoSelectFile()
  264. expect(this.$scope.history.selection.file).to.deep.equal(
  265. this.mockedAddedFile
  266. )
  267. })
  268. it('should prefer a file with ops if the previously selected file is available but has no operations', function () {
  269. this.historyManager._previouslySelectedPathname =
  270. this.mockedReferencesFile.pathname
  271. this.historyManager.autoSelectFile()
  272. expect(this.$scope.history.selection.file.operation).to.exist
  273. })
  274. it('should ignore the previously selected file if it is not available', function () {
  275. this.historyManager._previouslySelectedPathname = 'non/existent.file'
  276. this.historyManager.autoSelectFile()
  277. expect(this.$scope.history.selection.file.pathname).to.not.equal(
  278. 'non/existent.file'
  279. )
  280. })
  281. })
  282. describe('without a previously selected file, with a list of files containing operations', function () {
  283. it('should prefer edited files', function () {
  284. this.historyManager.autoSelectFile()
  285. expect(this.$scope.history.selection.file).to.deep.equal(
  286. this.mockedEditedFile
  287. )
  288. })
  289. it('should prefer added files if no edited files are present', function () {
  290. const indexOfEditedFile = this.$scope.history.selection.files.indexOf(
  291. this.mockedEditedFile
  292. )
  293. this.$scope.history.selection.files.splice(indexOfEditedFile, 1)
  294. this.historyManager.autoSelectFile()
  295. expect(this.$scope.history.selection.file).to.deep.equal(
  296. this.mockedAddedFile
  297. )
  298. })
  299. it('should prefer renamed files if no edited or added files are present', function () {
  300. const indexOfEditedFile = this.$scope.history.selection.files.indexOf(
  301. this.mockedEditedFile
  302. )
  303. this.$scope.history.selection.files.splice(indexOfEditedFile, 1)
  304. const indexOfAddedFile = this.$scope.history.selection.files.indexOf(
  305. this.mockedAddedFile
  306. )
  307. this.$scope.history.selection.files.splice(indexOfAddedFile, 1)
  308. this.historyManager.autoSelectFile()
  309. expect(this.$scope.history.selection.file).to.deep.equal(
  310. this.mockedRenamedFile
  311. )
  312. })
  313. it('should prefer removed files if no edited, added or renamed files are present', function () {
  314. const indexOfEditedFile = this.$scope.history.selection.files.indexOf(
  315. this.mockedEditedFile
  316. )
  317. this.$scope.history.selection.files.splice(indexOfEditedFile, 1)
  318. const indexOfAddedFile = this.$scope.history.selection.files.indexOf(
  319. this.mockedAddedFile
  320. )
  321. this.$scope.history.selection.files.splice(indexOfAddedFile, 1)
  322. const indexOfRenamedFile =
  323. this.$scope.history.selection.files.indexOf(this.mockedRenamedFile)
  324. this.$scope.history.selection.files.splice(indexOfRenamedFile, 1)
  325. this.historyManager.autoSelectFile()
  326. expect(this.$scope.history.selection.file).to.deep.equal(
  327. this.mockedRemovedFile
  328. )
  329. })
  330. })
  331. describe('without a previously selected file, with a list of files without operations', function () {
  332. beforeEach(function () {
  333. this.mockedFilesListWithNoOps = [
  334. {
  335. pathname: 'main.tex',
  336. },
  337. {
  338. pathname: 'references.bib',
  339. },
  340. {
  341. pathname: 'other.tex',
  342. },
  343. {
  344. pathname: 'universe.jpg',
  345. },
  346. ]
  347. this.mockedMainTex = this.mockedFilesListWithNoOps[0]
  348. this.mockedReferencesFile = this.mockedFilesListWithNoOps[1]
  349. this.mockedOtherTexFile = this.mockedFilesListWithNoOps[2]
  350. this.$scope.history.selection.files = this.mockedFilesListWithNoOps
  351. })
  352. it('should prefer main.tex if it exists', function () {
  353. this.historyManager.autoSelectFile()
  354. expect(this.$scope.history.selection.file).to.deep.equal(
  355. this.mockedMainTex
  356. )
  357. })
  358. it('should prefer another tex file if main.tex does not exist', function () {
  359. const indexOfMainTex = this.$scope.history.selection.files.indexOf(
  360. this.mockedMainTex
  361. )
  362. this.$scope.history.selection.files.splice(indexOfMainTex, 1)
  363. this.historyManager.autoSelectFile()
  364. expect(this.$scope.history.selection.file).to.deep.equal(
  365. this.mockedOtherTexFile
  366. )
  367. })
  368. it('should pick the first available file if no tex files are available', function () {
  369. const indexOfMainTex = this.$scope.history.selection.files.indexOf(
  370. this.mockedMainTex
  371. )
  372. this.$scope.history.selection.files.splice(indexOfMainTex, 1)
  373. const indexOfOtherTexFile =
  374. this.$scope.history.selection.files.indexOf(this.mockedOtherTexFile)
  375. this.$scope.history.selection.files.splice(indexOfOtherTexFile, 1)
  376. this.historyManager.autoSelectFile()
  377. expect(this.$scope.history.selection.file).to.deep.equal(
  378. this.mockedReferencesFile
  379. )
  380. })
  381. })
  382. })
  383. describe('for point-in-time mode', function () {
  384. beforeEach(function () {
  385. this.$scope.history.viewMode = 'point_in_time'
  386. this.sampleUpdates[0] = {
  387. fromV: 4,
  388. toV: 5,
  389. meta: {
  390. users: [
  391. {
  392. first_name: 'john.doe',
  393. last_name: '',
  394. email: 'john.doe@domain.tld',
  395. id: '5b57299087712202fb599ab4',
  396. hue: 200,
  397. },
  398. ],
  399. start_ts: 1544021278346,
  400. end_ts: 1544021278346,
  401. },
  402. pathnames: ['main.tex'],
  403. project_ops: [
  404. {
  405. add: {
  406. pathname: 'chapters/chapter1.tex',
  407. },
  408. atV: 4,
  409. },
  410. {
  411. rename: {
  412. pathname: 'foo.tex',
  413. newPathname: 'bar.tex',
  414. },
  415. },
  416. ],
  417. }
  418. this.sampleUpdateEditedFile = this.sampleUpdates[0].pathnames[0]
  419. this.sampleUpdateAddedFile =
  420. this.sampleUpdates[0].project_ops[0].add.pathname
  421. this.sampleUpdateRenamedFile =
  422. this.sampleUpdates[0].project_ops[1].rename.newPathname
  423. this.$scope.history.updates = this.sampleUpdates
  424. this.$scope.history.selection.range = {
  425. fromV: this.sampleUpdates[0].toV,
  426. toV: this.sampleUpdates[0].toV,
  427. }
  428. this.mockedFilesList = [
  429. {
  430. pathname: 'main.tex',
  431. },
  432. {
  433. pathname: 'references.bib',
  434. },
  435. {
  436. pathname: 'universe.jpg',
  437. },
  438. {
  439. pathname: 'chapters/chapter2.tex',
  440. },
  441. {
  442. pathname: 'chapters/draft.tex',
  443. },
  444. {
  445. pathname: 'chapters/chapter3.tex',
  446. },
  447. {
  448. pathname: 'chapters/chapter1.tex',
  449. },
  450. {
  451. pathname: 'bar.tex',
  452. },
  453. ]
  454. this.$scope.history.selection.files = this.mockedFilesList
  455. })
  456. describe('with a previously selected file', function () {
  457. it('should prefer the previously selected file if it is available and has operations', function () {
  458. this.historyManager._previouslySelectedPathname = 'main.tex'
  459. this.historyManager.autoSelectFile()
  460. expect(this.$scope.history.selection.file.pathname).to.equal(
  461. this.sampleUpdateEditedFile
  462. )
  463. })
  464. it('should prefer a file with ops if the previously selected file is available but has no operations', function () {
  465. this.historyManager._previouslySelectedPathname = 'main.tex'
  466. this.sampleUpdates[0].pathnames = []
  467. this.historyManager.autoSelectFile()
  468. expect(this.$scope.history.selection.file.pathname).to.equal(
  469. this.sampleUpdateAddedFile
  470. )
  471. })
  472. it('should ignore the previously selected file if it is not available', function () {
  473. this.historyManager._previouslySelectedPathname = 'non/existent.file'
  474. this.historyManager.autoSelectFile()
  475. expect(this.$scope.history.selection.file.pathname).to.not.equal(
  476. 'non/existent.file'
  477. )
  478. })
  479. })
  480. describe('without a previously selected file, with a list of files containing operations', function () {
  481. it('should prefer edited files', function () {
  482. this.historyManager.autoSelectFile()
  483. expect(this.$scope.history.selection.file.pathname).to.equal(
  484. this.sampleUpdateEditedFile
  485. )
  486. })
  487. it('should prefer added files if no edited files are present', function () {
  488. this.sampleUpdates[0].pathnames = []
  489. this.historyManager.autoSelectFile()
  490. expect(this.$scope.history.selection.file.pathname).to.equal(
  491. this.sampleUpdateAddedFile
  492. )
  493. })
  494. it('should prefer renamed files if no edited or added files are present', function () {
  495. this.sampleUpdates[0].pathnames = []
  496. this.sampleUpdates[0].project_ops.shift()
  497. this.historyManager.autoSelectFile()
  498. expect(this.$scope.history.selection.file.pathname).to.equal(
  499. this.sampleUpdateRenamedFile
  500. )
  501. })
  502. })
  503. describe('without a previously selected file, with a list of files without operations', function () {
  504. beforeEach(function () {
  505. this.sampleUpdates[0].pathnames = []
  506. this.sampleUpdates[0].project_ops = []
  507. this.mockedMainTex = this.mockedFilesList[0]
  508. this.mockedReferencesFile = this.mockedFilesList[1]
  509. })
  510. it('should prefer main.tex if it exists', function () {
  511. this.historyManager.autoSelectFile()
  512. expect(this.$scope.history.selection.file.pathname).to.equal(
  513. this.mockedMainTex.pathname
  514. )
  515. })
  516. it('should prefer another tex file if main.tex does not exist', function () {
  517. const indexOfMainTex = this.$scope.history.selection.files.indexOf(
  518. this.mockedMainTex
  519. )
  520. this.$scope.history.selection.files.splice(indexOfMainTex, 1)
  521. this.historyManager.autoSelectFile()
  522. expect(this.$scope.history.selection.file.pathname).to.match(/.tex$/)
  523. })
  524. })
  525. })
  526. describe('_loadLabels', function () {
  527. it('should return labels list as is if there is a label for the last version', function () {
  528. const labels = [
  529. {
  530. id: '1',
  531. version: 1,
  532. comment: 'foo',
  533. created_at: new Date().toISOString(),
  534. },
  535. {
  536. id: '2',
  537. version: 2,
  538. comment: 'bar',
  539. created_at: new Date().toISOString(),
  540. },
  541. {
  542. id: '3',
  543. version: 3,
  544. comment: 'baz',
  545. created_at: new Date().toISOString(),
  546. },
  547. ]
  548. const lastUpdate = 3
  549. const labelsResult = this.historyManager._loadLabels(labels, lastUpdate)
  550. expect(labelsResult).to.have.members(labels)
  551. })
  552. it('should return a labels list with a pseudo current state label if there is no label for the last version', function () {
  553. const labels = [
  554. {
  555. id: '1',
  556. version: 1,
  557. comment: 'foo',
  558. created_at: new Date().toISOString(),
  559. },
  560. {
  561. id: '2',
  562. version: 2,
  563. comment: 'bar',
  564. created_at: new Date().toISOString(),
  565. },
  566. {
  567. id: '3',
  568. version: 3,
  569. comment: 'baz',
  570. created_at: new Date().toISOString(),
  571. },
  572. ]
  573. const lastUpdate = 5
  574. const labelsResult = this.historyManager._loadLabels(labels, lastUpdate)
  575. expect(labelsResult).to.include.members(labels)
  576. expect(labelsResult[0].isPseudoCurrentStateLabel).to.equal(true)
  577. expect(labelsResult[0].version).to.equal(5)
  578. })
  579. it('should keep pseudo label when deleting label', function () {
  580. this.historyManager.$scope.history.labels = [
  581. {
  582. id: '1',
  583. version: 1,
  584. comment: 'foo',
  585. created_at: new Date().toISOString(),
  586. },
  587. ]
  588. const lastUpdate = 5
  589. this.historyManager.$scope.history.labels =
  590. this.historyManager._loadLabels(
  591. this.historyManager.$scope.history.labels,
  592. lastUpdate
  593. )
  594. expect(
  595. this.historyManager.$scope.history.labels[0].isPseudoCurrentStateLabel
  596. ).to.equal(true)
  597. this.historyManager.$scope.history.labels =
  598. this.historyManager._loadLabels([], lastUpdate)
  599. expect(
  600. this.historyManager.$scope.history.labels[0].isPseudoCurrentStateLabel
  601. ).to.equal(true)
  602. expect(this.historyManager.$scope.history.labels[0].version).to.equal(5)
  603. })
  604. })
  605. })
  606. })