editor-providers.jsx 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. // Disable prop type checks for test harnesses
  2. /* eslint-disable react/prop-types */
  3. import sinon from 'sinon'
  4. import { get, merge } from 'lodash'
  5. import { SocketIOMock } from '@/ide/connection/SocketIoShim'
  6. import { IdeContext } from '@/shared/context/ide-context'
  7. import React, { useEffect, useState } from 'react'
  8. import {
  9. createReactScopeValueStore,
  10. IdeReactContext,
  11. } from '@/features/ide-react/context/ide-react-context'
  12. import { IdeEventEmitter } from '@/features/ide-react/create-ide-event-emitter'
  13. import { ReactScopeEventEmitter } from '@/features/ide-react/scope-event-emitter/react-scope-event-emitter'
  14. import { ConnectionContext } from '@/features/ide-react/context/connection-context'
  15. import { EventLog } from '@/features/ide-react/editor/event-log'
  16. import { ReactContextRoot } from '@/features/ide-react/context/react-context-root'
  17. // these constants can be imported in tests instead of
  18. // using magic strings
  19. export const PROJECT_ID = 'project123'
  20. export const PROJECT_NAME = 'project-name'
  21. export const USER_ID = '123abd'
  22. export const USER_EMAIL = 'testuser@example.com'
  23. const defaultUserSettings = {
  24. pdfViewer: 'pdfjs',
  25. fontSize: 12,
  26. fontFamily: 'monaco',
  27. lineHeight: 'normal',
  28. editorTheme: 'textmate',
  29. overallTheme: '',
  30. mode: 'default',
  31. autoComplete: true,
  32. autoPairDelimiters: true,
  33. trackChanges: true,
  34. syntaxValidation: false,
  35. mathPreview: true,
  36. }
  37. export function EditorProviders({
  38. user = { id: USER_ID, email: USER_EMAIL },
  39. projectId = PROJECT_ID,
  40. projectOwner = {
  41. _id: '124abd',
  42. email: 'owner@example.com',
  43. },
  44. rootDocId = '_root_doc_id',
  45. socket = new SocketIOMock(),
  46. isRestrictedTokenMember = false,
  47. clsiServerId = '1234',
  48. scope = {},
  49. features = {
  50. referencesSearch: true,
  51. },
  52. permissionsLevel = 'owner',
  53. children,
  54. rootFolder = [
  55. {
  56. _id: 'root-folder-id',
  57. name: 'rootFolder',
  58. docs: [],
  59. folders: [],
  60. fileRefs: [],
  61. },
  62. ],
  63. ui = { view: 'editor', pdfLayout: 'sideBySide', chatOpen: true },
  64. fileTreeManager = {
  65. findEntityById: () => null,
  66. findEntityByPath: () => null,
  67. getEntityPath: () => '',
  68. getRootDocDirname: () => '',
  69. getPreviewByPath: path => ({ url: path, extension: 'png' }),
  70. },
  71. editorManager = {
  72. getCurrentDocumentId: () => 'foo',
  73. getCurrentDocValue: () => {},
  74. openDoc: sinon.stub(),
  75. },
  76. userSettings = {},
  77. providers = {},
  78. }) {
  79. window.metaAttributesCache.set(
  80. 'ol-gitBridgePublicBaseUrl',
  81. 'https://git.overleaf.test'
  82. )
  83. window.metaAttributesCache.set(
  84. 'ol-isRestrictedTokenMember',
  85. isRestrictedTokenMember
  86. )
  87. window.metaAttributesCache.set(
  88. 'ol-userSettings',
  89. merge({}, defaultUserSettings, userSettings)
  90. )
  91. const $scope = merge(
  92. {
  93. user,
  94. editor: {
  95. sharejs_doc: {
  96. doc_id: 'test-doc',
  97. getSnapshot: () => 'some doc content',
  98. },
  99. },
  100. project: {
  101. _id: projectId,
  102. name: PROJECT_NAME,
  103. owner: projectOwner,
  104. features,
  105. rootDoc_id: rootDocId,
  106. rootFolder,
  107. },
  108. ui,
  109. $watch: (path, callback) => {
  110. callback(get($scope, path))
  111. return () => null
  112. },
  113. $on: sinon.stub(),
  114. $applyAsync: sinon.stub(),
  115. permissionsLevel,
  116. },
  117. scope
  118. )
  119. window._ide = {
  120. $scope,
  121. socket,
  122. clsiServerId,
  123. editorManager,
  124. fileTreeManager,
  125. }
  126. // Add details for useUserContext
  127. window.metaAttributesCache.set('ol-user', { ...user, features })
  128. window.metaAttributesCache.set('ol-project_id', projectId)
  129. return (
  130. <ReactContextRoot
  131. providers={{
  132. ConnectionProvider,
  133. IdeReactProvider,
  134. ...providers,
  135. }}
  136. >
  137. {children}
  138. </ReactContextRoot>
  139. )
  140. }
  141. const ConnectionProvider = ({ children }) => {
  142. const [value] = useState(() => ({
  143. socket: window._ide.socket,
  144. connectionState: {
  145. readyState: WebSocket.OPEN,
  146. forceDisconnected: false,
  147. inactiveDisconnect: false,
  148. reconnectAt: null,
  149. forcedDisconnectDelay: 0,
  150. lastConnectionAttempt: 0,
  151. error: '',
  152. },
  153. isConnected: true,
  154. isStillReconnecting: false,
  155. secondsUntilReconnect: () => 0,
  156. tryReconnectNow: () => {},
  157. registerUserActivity: () => {},
  158. disconnect: () => {},
  159. }))
  160. return (
  161. <ConnectionContext.Provider value={value}>
  162. {children}
  163. </ConnectionContext.Provider>
  164. )
  165. }
  166. const IdeReactProvider = ({ children }) => {
  167. const [startedFreeTrial, setStartedFreeTrial] = useState(false)
  168. const [ideReactContextValue] = useState(() => ({
  169. projectId: PROJECT_ID,
  170. eventEmitter: new IdeEventEmitter(),
  171. eventLog: new EventLog(),
  172. startedFreeTrial,
  173. setStartedFreeTrial,
  174. reportError: () => {},
  175. projectJoined: true,
  176. }))
  177. const [ideContextValue] = useState(() => {
  178. const ide = window._ide
  179. const scopeStore = createReactScopeValueStore(PROJECT_ID)
  180. for (const [key, value] of Object.entries(ide.$scope)) {
  181. // TODO: path for nested entries
  182. scopeStore.set(key, value)
  183. }
  184. scopeStore.set('editor.sharejs_doc', ide.$scope.editor.sharejs_doc)
  185. scopeStore.set('ui.chatOpen', ide.$scope.ui.chatOpen)
  186. const scopeEventEmitter = new ReactScopeEventEmitter(new IdeEventEmitter())
  187. return {
  188. ...ide,
  189. scopeStore,
  190. scopeEventEmitter,
  191. }
  192. })
  193. useEffect(() => {
  194. window.overleaf = {
  195. ...window.overleaf,
  196. unstable: {
  197. ...window.overleaf?.unstable,
  198. store: ideContextValue.scopeStore,
  199. },
  200. }
  201. }, [ideContextValue.scopeStore])
  202. return (
  203. <IdeReactContext.Provider value={ideReactContextValue}>
  204. <IdeContext.Provider value={ideContextValue}>
  205. {children}
  206. </IdeContext.Provider>
  207. </IdeReactContext.Provider>
  208. )
  209. }