project-snapshot.test.ts 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. import { expect } from 'chai'
  2. import fetchMock from 'fetch-mock'
  3. import { ProjectSnapshot } from '@/infrastructure/project-snapshot'
  4. describe('ProjectSnapshot', function () {
  5. let snapshot: ProjectSnapshot
  6. const projectId = 'project-id'
  7. beforeEach(function () {
  8. snapshot = new ProjectSnapshot(projectId)
  9. })
  10. describe('before initialization', function () {
  11. describe('getDocPaths()', function () {
  12. it('returns an empty string', function () {
  13. expect(snapshot.getDocPaths()).to.deep.equal([])
  14. })
  15. })
  16. describe('getDocContents()', function () {
  17. it('returns null', function () {
  18. expect(snapshot.getDocContents('main.tex')).to.be.null
  19. })
  20. })
  21. })
  22. const files = {
  23. 'main.tex': {
  24. contents: '\\documentclass{article}\netc.',
  25. hash: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
  26. },
  27. 'hello.txt': {
  28. contents: 'Hello history!',
  29. hash: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
  30. },
  31. 'goodbye.txt': {
  32. contents: "We're done here",
  33. hash: 'dddddddddddddddddddddddddddddddddddddddd',
  34. },
  35. }
  36. const chunk = {
  37. history: {
  38. snapshot: {
  39. files: {},
  40. },
  41. changes: [
  42. {
  43. operations: [
  44. {
  45. pathname: 'hello.txt',
  46. file: {
  47. hash: files['hello.txt'].hash,
  48. stringLength: files['hello.txt'].contents.length,
  49. },
  50. },
  51. {
  52. pathname: 'main.tex',
  53. file: {
  54. hash: files['main.tex'].hash,
  55. stringLength: files['main.tex'].contents.length,
  56. },
  57. },
  58. {
  59. pathname: 'frog.jpg',
  60. file: {
  61. hash: 'cccccccccccccccccccccccccccccccccccccccc',
  62. byteLength: 97080,
  63. },
  64. },
  65. ],
  66. timestamp: '2025-01-01T12:00:00.000Z',
  67. },
  68. ],
  69. },
  70. startVersion: 0,
  71. }
  72. async function initializeSnapshot() {
  73. fetchMock.postOnce(`/project/${projectId}/flush`, 200)
  74. fetchMock.getOnce(`/project/${projectId}/latest/history`, { chunk })
  75. fetchMock.getOnce(
  76. `/project/${projectId}/blob/${files['main.tex'].hash}`,
  77. files['main.tex'].contents
  78. )
  79. fetchMock.getOnce(
  80. `/project/${projectId}/blob/${files['hello.txt'].hash}`,
  81. files['hello.txt'].contents
  82. )
  83. await snapshot.refresh()
  84. expect(fetchMock.done()).to.be.true
  85. fetchMock.reset()
  86. }
  87. describe('after initialization', function () {
  88. beforeEach(initializeSnapshot)
  89. describe('getDocPaths()', function () {
  90. it('returns the editable docs', function () {
  91. expect(snapshot.getDocPaths()).to.have.members([
  92. 'main.tex',
  93. 'hello.txt',
  94. ])
  95. })
  96. })
  97. describe('getDocContents()', function () {
  98. it('returns the doc contents', function () {
  99. expect(snapshot.getDocContents('main.tex')).to.equal(
  100. files['main.tex'].contents
  101. )
  102. })
  103. it('returns null for binary files', function () {
  104. expect(snapshot.getDocContents('frog.jpg')).to.be.null
  105. })
  106. it('returns null for inexistent files', function () {
  107. expect(snapshot.getDocContents('does-not-exist.txt')).to.be.null
  108. })
  109. })
  110. })
  111. const changes = [
  112. {
  113. operations: [
  114. {
  115. pathname: 'hello.txt',
  116. textOperation: ['Quote: ', files['hello.txt'].contents.length],
  117. },
  118. {
  119. pathname: 'goodbye.txt',
  120. file: {
  121. hash: files['goodbye.txt'].hash,
  122. stringLength: files['goodbye.txt'].contents.length,
  123. },
  124. },
  125. ],
  126. timestamp: '2025-01-01T13:00:00.000Z',
  127. },
  128. ]
  129. async function refreshSnapshot() {
  130. fetchMock.postOnce(`/project/${projectId}/flush`, 200, { repeat: 2 })
  131. fetchMock.getOnce(`/project/${projectId}/changes?since=1`, changes)
  132. fetchMock.getOnce(
  133. `/project/${projectId}/blob/${files['goodbye.txt'].hash}`,
  134. files['goodbye.txt'].contents
  135. )
  136. await snapshot.refresh()
  137. expect(fetchMock.done()).to.be.true
  138. fetchMock.reset()
  139. }
  140. describe('after refresh', function () {
  141. beforeEach(initializeSnapshot)
  142. beforeEach(refreshSnapshot)
  143. afterEach(function () {
  144. fetchMock.reset()
  145. })
  146. describe('getDocPaths()', function () {
  147. it('returns the editable docs', function () {
  148. expect(snapshot.getDocPaths()).to.have.members([
  149. 'main.tex',
  150. 'hello.txt',
  151. 'goodbye.txt',
  152. ])
  153. })
  154. })
  155. describe('getDocCotents()', function () {
  156. it('returns the up to date content', function () {
  157. expect(snapshot.getDocContents('hello.txt')).to.equal(
  158. `Quote: ${files['hello.txt'].contents}`
  159. )
  160. })
  161. it('returns contents of new files', function () {
  162. expect(snapshot.getDocContents('goodbye.txt')).to.equal(
  163. files['goodbye.txt'].contents
  164. )
  165. })
  166. })
  167. })
  168. })