HistoryV2ManagerTests.js 20 KB

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