Просмотр исходного кода

[web] Convert EditorProviders and some test files to Typescript (#26512)

* Rename files to tsx

* Update types

* Remove props that aren't typed

* Add `LayoutContextOwnStates`

* Use `LayoutContextOwnStates`

* Ignore ts errors about `SocketIOMock`

* Address comments: remove `satisfies`, update `BroadcastChannel` fixture

* Add types to `makeEditorOpenDocProvider`. Update `openDocId`->`currentDocumentId`

* misc.

* Type sockets as `SocketIOMock & Socket`

* Fix remaining typing errors

* Fix type of `ideReactContextValue`

GitOrigin-RevId: 2734ac707517d56c452b0bf06ea3438f947a64be
Antoine Clausse 1 год назад
Родитель
Сommit
07b1701b72
17 измененных файлов с 186 добавлено и 107 удалено
  1. 23 21
      services/web/frontend/js/features/editor-navigation-toolbar/components/toolbar-header.tsx
  2. 6 3
      services/web/frontend/js/features/ide-react/context/editor-open-doc-context.tsx
  3. 2 2
      services/web/frontend/js/features/ide-react/context/react-context-root.tsx
  4. 13 10
      services/web/frontend/js/shared/context/layout-context.tsx
  5. 2 1
      services/web/test/frontend/components/pdf-preview/pdf-synctex-controls.spec.tsx
  6. 0 0
      services/web/test/frontend/features/editor-navigation-toolbar/components/chat-toggle-button.test.tsx
  7. 7 5
      services/web/test/frontend/features/editor-navigation-toolbar/components/layout-dropdown-button.test.tsx
  8. 14 2
      services/web/test/frontend/features/editor-navigation-toolbar/components/online-users-widget.test.tsx
  9. 0 0
      services/web/test/frontend/features/editor-navigation-toolbar/components/project-name-editable-label.test.tsx
  10. 12 7
      services/web/test/frontend/features/editor-navigation-toolbar/components/toolbar-header.test.tsx
  11. 3 2
      services/web/test/frontend/features/file-tree/components/file-tree-root.spec.tsx
  12. 3 2
      services/web/test/frontend/features/file-tree/flows/create-folder.spec.tsx
  13. 7 6
      services/web/test/frontend/features/file-tree/flows/delete-entity.spec.tsx
  14. 3 2
      services/web/test/frontend/features/file-tree/flows/rename-entity.spec.tsx
  15. 71 24
      services/web/test/frontend/helpers/editor-providers.tsx
  16. 0 20
      services/web/test/frontend/helpers/render-with-context.jsx
  17. 20 0
      services/web/test/frontend/helpers/render-with-context.tsx

+ 23 - 21
services/web/frontend/js/features/editor-navigation-toolbar/components/toolbar-header.tsx

@@ -39,6 +39,28 @@ const enableROMirrorOnClient =
   new URLSearchParams(window.location.search).get('ro-mirror-on-client') ===
     'enabled'
 
+export type ToolbarHeaderProps = {
+  cobranding: Cobranding | undefined
+  onShowLeftMenuClick: () => void
+  chatIsOpen: boolean
+  toggleChatOpen: () => void
+  reviewPanelOpen: boolean
+  toggleReviewPanelOpen: (e: React.MouseEvent) => void
+  historyIsOpen: boolean
+  toggleHistoryOpen: () => void
+  unreadMessageCount: number
+  onlineUsers: OnlineUser[]
+  goToUser: (user: OnlineUser) => void
+  isRestrictedTokenMember: boolean | undefined
+  hasPublishPermissions: boolean
+  chatVisible: boolean
+  projectName: string
+  renameProject: (name: string) => void
+  hasRenamePermissions: boolean
+  openShareModal: () => void
+  trackChangesVisible: boolean | undefined
+}
+
 const ToolbarHeader = React.memo(function ToolbarHeader({
   cobranding,
   onShowLeftMenuClick,
@@ -59,27 +81,7 @@ const ToolbarHeader = React.memo(function ToolbarHeader({
   hasRenamePermissions,
   openShareModal,
   trackChangesVisible,
-}: {
-  cobranding: Cobranding | undefined
-  onShowLeftMenuClick: () => void
-  chatIsOpen: boolean
-  toggleChatOpen: () => void
-  reviewPanelOpen: boolean
-  toggleReviewPanelOpen: (e: React.MouseEvent) => void
-  historyIsOpen: boolean
-  toggleHistoryOpen: () => void
-  unreadMessageCount: number
-  onlineUsers: OnlineUser[]
-  goToUser: (user: OnlineUser) => void
-  isRestrictedTokenMember: boolean | undefined
-  hasPublishPermissions: boolean
-  chatVisible: boolean
-  projectName: string
-  renameProject: (name: string) => void
-  hasRenamePermissions: boolean
-  openShareModal: () => void
-  trackChangesVisible: boolean | undefined
-}) {
+}: ToolbarHeaderProps) {
   const chatEnabled = getMeta('ol-capabilities')?.includes('chat')
 
   const { t } = useTranslation()

+ 6 - 3
services/web/frontend/js/features/ide-react/context/editor-open-doc-context.tsx

@@ -11,12 +11,15 @@ import { DocId } from '../../../../../types/project-settings'
 import useExposedState from '@/shared/hooks/use-exposed-state'
 import { DocumentContainer } from '@/features/ide-react/editor/document-container'
 
-type EditorOpenDocContextValue = {
+export interface EditorOpenDocContextState {
   currentDocumentId: DocId | null
-  setCurrentDocumentId: Dispatch<SetStateAction<DocId | null>>
   openDocName: string | null
-  setOpenDocName: Dispatch<SetStateAction<string | null>>
   currentDocument: DocumentContainer | null
+}
+
+interface EditorOpenDocContextValue extends EditorOpenDocContextState {
+  setCurrentDocumentId: Dispatch<SetStateAction<DocId | null>>
+  setOpenDocName: Dispatch<SetStateAction<string | null>>
   setCurrentDocument: Dispatch<SetStateAction<DocumentContainer | null>>
 }
 

+ 2 - 2
services/web/frontend/js/features/ide-react/context/react-context-root.tsx

@@ -1,4 +1,4 @@
-import { FC } from 'react'
+import React, { FC, PropsWithChildren } from 'react'
 import { ChatProvider } from '@/features/chat/context/chat-context'
 import { ConnectionProvider } from './connection-context'
 import { DetachCompileProvider } from '@/shared/context/detach-compile-context'
@@ -32,7 +32,7 @@ import { CommandRegistryProvider } from './command-registry-context'
 
 export const ReactContextRoot: FC<
   React.PropsWithChildren<{
-    providers?: Record<string, FC>
+    providers?: Record<string, FC<PropsWithChildren>>
   }>
 > = ({ children, providers = {} }) => {
   const Providers = {

+ 13 - 10
services/web/frontend/js/shared/context/layout-context.tsx

@@ -25,37 +25,40 @@ import usePersistedState from '@/shared/hooks/use-persisted-state'
 export type IdeLayout = 'sideBySide' | 'flat'
 export type IdeView = 'editor' | 'file' | 'pdf' | 'history'
 
-export type LayoutContextValue = {
+export type LayoutContextOwnStates = {
+  view: IdeView | null
+  chatIsOpen: boolean
+  reviewPanelOpen: boolean
+  miniReviewPanelVisible: boolean
+  leftMenuShown: boolean
+  loadingStyleSheet: boolean
+  pdfLayout: IdeLayout
+  projectSearchIsOpen: boolean
+  openFile: BinaryFile | null
+}
+
+export type LayoutContextValue = LayoutContextOwnStates & {
   reattach: () => void
   detach: () => void
   detachIsLinked: boolean
   detachRole: DetachRole
   changeLayout: (newLayout: IdeLayout, newView?: IdeView) => void
-  view: IdeView | null
   setView: (view: IdeView | null) => void
-  chatIsOpen: boolean
   setChatIsOpen: Dispatch<SetStateAction<LayoutContextValue['chatIsOpen']>>
-  reviewPanelOpen: boolean
   setReviewPanelOpen: Dispatch<
     SetStateAction<LayoutContextValue['reviewPanelOpen']>
   >
-  miniReviewPanelVisible: boolean
   setMiniReviewPanelVisible: Dispatch<
     SetStateAction<LayoutContextValue['miniReviewPanelVisible']>
   >
-  leftMenuShown: boolean
   setLeftMenuShown: Dispatch<
     SetStateAction<LayoutContextValue['leftMenuShown']>
   >
-  loadingStyleSheet: boolean
   setLoadingStyleSheet: Dispatch<
     SetStateAction<LayoutContextValue['loadingStyleSheet']>
   >
-  pdfLayout: IdeLayout
   pdfPreviewOpen: boolean
-  projectSearchIsOpen: boolean
   setProjectSearchIsOpen: Dispatch<SetStateAction<boolean>>
-  openFile: BinaryFile | null
   setOpenFile: Dispatch<SetStateAction<BinaryFile | null>>
 }
 

+ 2 - 1
services/web/test/frontend/components/pdf-preview/pdf-synctex-controls.spec.tsx

@@ -80,6 +80,7 @@ function mockProviders() {
   return {
     EditorOpenDocProvider: makeEditorOpenDocProvider({
       openDocName: 'main.tex',
+      currentDocumentId: null,
       currentDocument: {
         doc_id: 'test-doc',
         getSnapshot: () => 'some doc content',
@@ -87,7 +88,7 @@ function mockProviders() {
         on: () => {},
         off: () => {},
         leaveAndCleanUpPromise: () => Promise.resolve(),
-      },
+      } as any,
     }),
   }
 }

+ 0 - 0
services/web/test/frontend/features/editor-navigation-toolbar/components/chat-toggle-button.test.jsx → services/web/test/frontend/features/editor-navigation-toolbar/components/chat-toggle-button.test.tsx


+ 7 - 5
services/web/test/frontend/features/editor-navigation-toolbar/components/layout-dropdown-button.test.jsx → services/web/test/frontend/features/editor-navigation-toolbar/components/layout-dropdown-button.test.tsx

@@ -5,12 +5,13 @@ import { screen, waitFor } from '@testing-library/react'
 import LayoutDropdownButton from '../../../../../frontend/js/features/editor-navigation-toolbar/components/layout-dropdown-button'
 import { renderWithEditorContext } from '../../../helpers/render-with-context'
 import * as eventTracking from '@/infrastructure/event-tracking'
+import type { LayoutContextOwnStates } from '@/shared/context/layout-context'
 
 describe('<LayoutDropdownButton />', function () {
-  let openStub
-  let sendMBSpy
+  let openStub: sinon.SinonStub
+  let sendMBSpy: sinon.SinonSpy
 
-  const defaultLayout = {
+  const defaultLayout: Partial<LayoutContextOwnStates> = {
     pdfLayout: 'flat',
     view: 'pdf',
     chatIsOpen: false,
@@ -161,9 +162,10 @@ describe('<LayoutDropdownButton />', function () {
   })
 
   describe('on detach', async function () {
-    let originalBroadcastChannel
+    const originalBroadcastChannel = window.BroadcastChannel
     beforeEach(async function () {
-      window.BroadcastChannel = originalBroadcastChannel || true // ensure that window.BroadcastChannel is truthy
+      // @ts-expect-error
+      window.BroadcastChannel = true // ensure that window.BroadcastChannel is truthy
 
       renderWithEditorContext(<LayoutDropdownButton />, {
         layoutContext: { ...defaultLayout, view: 'editor' },

+ 14 - 2
services/web/test/frontend/features/editor-navigation-toolbar/components/online-users-widget.test.jsx → services/web/test/frontend/features/editor-navigation-toolbar/components/online-users-widget.test.tsx

@@ -8,12 +8,16 @@ describe('<OnlineUsersWidget />', function () {
   const defaultProps = {
     onlineUsers: [
       {
+        id: 'test_user',
         user_id: 'test_user',
         name: 'test_user',
+        email: 'test_email',
       },
       {
+        id: 'another_test_user',
         user_id: 'another_test_user',
         name: 'another_test_user',
+        email: 'another_test_email',
       },
     ],
     goToUser: () => {},
@@ -44,8 +48,10 @@ describe('<OnlineUsersWidget />', function () {
       fireEvent.click(icon)
 
       expect(props.goToUser).to.be.calledWith({
-        name: 'test_user',
+        id: 'test_user',
         user_id: 'test_user',
+        name: 'test_user',
+        email: 'test_email',
       })
     })
   })
@@ -55,12 +61,16 @@ describe('<OnlineUsersWidget />', function () {
       ...defaultProps,
       onlineUsers: defaultProps.onlineUsers.concat([
         {
+          id: 'user_3',
           user_id: 'user_3',
           name: 'user_3',
+          email: 'user_3',
         },
         {
+          id: 'user_4',
           user_id: 'user_4',
           name: 'user_4',
+          email: 'user_4',
         },
       ]),
     }
@@ -96,8 +106,10 @@ describe('<OnlineUsersWidget />', function () {
       fireEvent.click(icon)
 
       expect(testProps.goToUser).to.be.calledWith({
-        name: 'user_3',
+        id: 'user_3',
         user_id: 'user_3',
+        name: 'user_3',
+        email: 'user_3',
       })
     })
   })

+ 0 - 0
services/web/test/frontend/features/editor-navigation-toolbar/components/project-name-editable-label.test.jsx → services/web/test/frontend/features/editor-navigation-toolbar/components/project-name-editable-label.test.tsx


+ 12 - 7
services/web/test/frontend/features/editor-navigation-toolbar/components/toolbar-header.test.jsx → services/web/test/frontend/features/editor-navigation-toolbar/components/toolbar-header.test.tsx

@@ -1,11 +1,13 @@
 import { expect } from 'chai'
 import { screen } from '@testing-library/react'
 
-import ToolbarHeader from '../../../../../frontend/js/features/editor-navigation-toolbar/components/toolbar-header'
+import ToolbarHeader, {
+  type ToolbarHeaderProps,
+} from '../../../../../frontend/js/features/editor-navigation-toolbar/components/toolbar-header'
 import { renderWithEditorContext } from '../../../helpers/render-with-context'
 
 describe('<ToolbarHeader />', function () {
-  const defaultProps = {
+  const defaultProps: ToolbarHeaderProps = {
     onShowLeftMenuClick: () => {},
     toggleChatOpen: () => {},
     toggleReviewPanelOpen: () => {},
@@ -19,11 +21,12 @@ describe('<ToolbarHeader />', function () {
     hasPublishPermissions: true,
     chatVisible: true,
     trackChangesVisible: true,
-    handleChangeLayout: () => {},
-    pdfLayout: 'sideBySide',
-    view: 'editor',
-    reattach: () => {},
-    detach: () => {},
+    cobranding: undefined,
+    isRestrictedTokenMember: false,
+    hasRenamePermissions: true,
+    historyIsOpen: false,
+    chatIsOpen: false,
+    reviewPanelOpen: false,
   }
 
   beforeEach(function () {
@@ -40,6 +43,8 @@ describe('<ToolbarHeader />', function () {
       const props = {
         ...defaultProps,
         cobranding: {
+          brandId: 12,
+          brandVariationId: 12,
           brandVariationHomeUrl: 'http://cobranding',
           brandVariationName: 'variation',
           logoImgUrl: 'http://cobranding/logo',

+ 3 - 2
services/web/test/frontend/features/file-tree/components/file-tree-root.spec.tsx

@@ -1,6 +1,7 @@
 import FileTreeRoot from '../../../../../frontend/js/features/file-tree/components/file-tree-root'
 import { EditorProviders } from '../../../helpers/editor-providers'
 import { SocketIOMock } from '@/ide/connection/SocketIoShim'
+import type { Socket } from '@/features/ide-react/connection/types/socket'
 
 describe('<FileTreeRoot/>', function () {
   beforeEach(function () {
@@ -245,9 +246,9 @@ describe('<FileTreeRoot/>', function () {
   })
 
   describe('when deselecting files', function () {
-    let socket: SocketIOMock
+    let socket: SocketIOMock & Socket
     beforeEach(function () {
-      socket = new SocketIOMock()
+      socket = new SocketIOMock() as any
       const rootFolder = [
         {
           _id: 'root-folder-id',

+ 3 - 2
services/web/test/frontend/features/file-tree/flows/create-folder.spec.tsx

@@ -1,11 +1,12 @@
 import FileTreeRoot from '../../../../../frontend/js/features/file-tree/components/file-tree-root'
 import { EditorProviders } from '../../../helpers/editor-providers'
 import { SocketIOMock } from '@/ide/connection/SocketIoShim'
+import type { Socket } from '@/features/ide-react/connection/types/socket'
 
 describe('FileTree Create Folder Flow', function () {
-  let socket: SocketIOMock
+  let socket: SocketIOMock & Socket
   beforeEach(function () {
-    socket = new SocketIOMock()
+    socket = new SocketIOMock() as any
     cy.window().then(win => {
       win.metaAttributesCache.set('ol-user', { id: 'user1' })
     })

+ 7 - 6
services/web/test/frontend/features/file-tree/flows/delete-entity.spec.tsx

@@ -1,6 +1,7 @@
 import FileTreeRoot from '../../../../../frontend/js/features/file-tree/components/file-tree-root'
 import { EditorProviders } from '../../../helpers/editor-providers'
 import { SocketIOMock } from '@/ide/connection/SocketIoShim'
+import type { Socket } from '@/features/ide-react/connection/types/socket'
 
 describe('FileTree Delete Entity Flow', function () {
   beforeEach(function () {
@@ -10,9 +11,9 @@ describe('FileTree Delete Entity Flow', function () {
   })
 
   describe('single entity', function () {
-    let socket: SocketIOMock
+    let socket: SocketIOMock & Socket
     beforeEach(function () {
-      socket = new SocketIOMock()
+      socket = new SocketIOMock() as any
       const rootFolder = [
         {
           _id: 'root-folder-id',
@@ -136,9 +137,9 @@ describe('FileTree Delete Entity Flow', function () {
   })
 
   describe('folders', function () {
-    let socket: SocketIOMock
+    let socket: SocketIOMock & Socket
     beforeEach(function () {
-      socket = new SocketIOMock()
+      socket = new SocketIOMock() as any
       const rootFolder = [
         {
           _id: 'root-folder-id',
@@ -207,9 +208,9 @@ describe('FileTree Delete Entity Flow', function () {
   })
 
   describe('multiple entities', function () {
-    let socket: SocketIOMock
+    let socket: SocketIOMock & Socket
     beforeEach(function () {
-      socket = new SocketIOMock()
+      socket = new SocketIOMock() as any
       const rootFolder = [
         {
           _id: 'root-folder-id',

+ 3 - 2
services/web/test/frontend/features/file-tree/flows/rename-entity.spec.tsx

@@ -1,6 +1,7 @@
 import FileTreeRoot from '../../../../../frontend/js/features/file-tree/components/file-tree-root'
 import { EditorProviders } from '../../../helpers/editor-providers'
 import { SocketIOMock } from '@/ide/connection/SocketIoShim'
+import type { Socket } from '@/features/ide-react/connection/types/socket'
 
 describe('FileTree Rename Entity Flow', function () {
   beforeEach(function () {
@@ -9,9 +10,9 @@ describe('FileTree Rename Entity Flow', function () {
     })
   })
 
-  let socket: SocketIOMock
+  let socket: SocketIOMock & Socket
   beforeEach(function () {
-    socket = new SocketIOMock()
+    socket = new SocketIOMock() as any
     const rootFolder = [
       {
         _id: 'root-folder-id',

+ 71 - 24
services/web/test/frontend/helpers/editor-providers.jsx → services/web/test/frontend/helpers/editor-providers.tsx

@@ -3,7 +3,14 @@
 import { merge } from 'lodash'
 import { SocketIOMock } from '@/ide/connection/SocketIoShim'
 import { IdeContext } from '@/shared/context/ide-context'
-import React, { useCallback, useEffect, useMemo, useState } from 'react'
+import React, {
+  useCallback,
+  useEffect,
+  useState,
+  useMemo,
+  type FC,
+  type PropsWithChildren,
+} from 'react'
 import {
   createReactScopeValueStore,
   IdeReactContext,
@@ -12,12 +19,25 @@ import { IdeEventEmitter } from '@/features/ide-react/create-ide-event-emitter'
 import { ReactScopeValueStore } from '@/features/ide-react/scope-value-store/react-scope-value-store'
 import { ReactScopeEventEmitter } from '@/features/ide-react/scope-event-emitter/react-scope-event-emitter'
 import { ConnectionContext } from '@/features/ide-react/context/connection-context'
-import { EditorOpenDocContext } from '@/features/ide-react/context/editor-open-doc-context'
+import {
+  EditorOpenDocContext,
+  type EditorOpenDocContextState,
+} from '@/features/ide-react/context/editor-open-doc-context'
 import { ReactContextRoot } from '@/features/ide-react/context/react-context-root'
 import useEventListener from '@/shared/hooks/use-event-listener'
 import useDetachLayout from '@/shared/hooks/use-detach-layout'
-import { LayoutContext } from '@/shared/context/layout-context'
 import useExposedState from '@/shared/hooks/use-exposed-state'
+import {
+  type IdeLayout,
+  type IdeView,
+  LayoutContext,
+  type LayoutContextValue,
+} from '@/shared/context/layout-context'
+import type { Socket } from '@/features/ide-react/connection/types/socket'
+import type { PermissionsLevel } from '@/features/ide-react/types/permissions'
+import type { Folder } from '../../../types/folder'
+import type { SocketDebuggingInfo } from '@/features/ide-react/connection/types/connection-state'
+import type { DocumentContainer } from '@/features/ide-react/editor/document-container'
 
 // these constants can be imported in tests instead of
 // using magic strings
@@ -41,10 +61,26 @@ const defaultUserSettings = {
   mathPreview: true,
 }
 
-/**
- * @typedef {import('@/shared/context/layout-context').LayoutContextValue} LayoutContextValue
- * @type Partial<LayoutContextValue>
- */
+export type EditorProvidersProps = {
+  user?: { id: string; email: string }
+  projectId?: string
+  projectOwner?: { _id: string; email: string }
+  rootDocId?: string
+  imageName?: string
+  compiler?: string
+  socket?: Socket
+  isRestrictedTokenMember?: boolean
+  scope?: Record<string, any>
+  features?: Record<string, boolean>
+  projectFeatures?: Record<string, boolean>
+  permissionsLevel?: PermissionsLevel
+  children?: React.ReactNode
+  rootFolder?: Folder[]
+  layoutContext?: Partial<LayoutContextValue>
+  userSettings?: Record<string, any>
+  providers?: Record<string, React.FC<React.PropsWithChildren<any>>>
+}
+
 const layoutContextDefault = {
   view: 'editor',
   openFile: null,
@@ -55,7 +91,7 @@ const layoutContextDefault = {
   projectSearchIsOpen: false,
   pdfLayout: 'sideBySide',
   loadingStyleSheet: false,
-}
+} satisfies Partial<LayoutContextValue>
 
 export function EditorProviders({
   user = { id: USER_ID, email: USER_EMAIL },
@@ -67,7 +103,7 @@ export function EditorProviders({
   rootDocId = '_root_doc_id',
   imageName = 'texlive-full:2024.1',
   compiler = 'pdflatex',
-  socket = new SocketIOMock(),
+  socket = new SocketIOMock() as any as Socket,
   isRestrictedTokenMember = false,
   scope: defaultScope = {},
   features = {
@@ -94,7 +130,7 @@ export function EditorProviders({
   layoutContext = layoutContextDefault,
   userSettings = {},
   providers = {},
-}) {
+}: EditorProvidersProps) {
   window.metaAttributesCache.set(
     'ol-gitBridgePublicBaseUrl',
     'https://git.overleaf.test'
@@ -121,7 +157,7 @@ export function EditorProviders({
           on: () => {},
           off: () => {},
           leaveAndCleanUpPromise: async () => {},
-        },
+        } as any as DocumentContainer,
         openDocName: null,
         currentDocumentId: null,
       },
@@ -150,7 +186,7 @@ export function EditorProviders({
         ConnectionProvider: makeConnectionProvider(socket),
         IdeReactProvider: makeIdeReactProvider(scope, socket),
         EditorOpenDocProvider: makeEditorOpenDocProvider({
-          openDocId: scope.editor.currentDocumentId,
+          currentDocumentId: scope.editor.currentDocumentId,
           openDocName: scope.editor.openDocName,
           currentDocument: scope.editor.sharejs_doc,
         }),
@@ -163,8 +199,8 @@ export function EditorProviders({
   )
 }
 
-const makeConnectionProvider = socket => {
-  const ConnectionProvider = ({ children }) => {
+const makeConnectionProvider = (socket: Socket) => {
+  const ConnectionProvider: FC<PropsWithChildren> = ({ children }) => {
     const [value] = useState(() => ({
       socket,
       connectionState: {
@@ -174,7 +210,7 @@ const makeConnectionProvider = socket => {
         reconnectAt: null,
         forcedDisconnectDelay: 0,
         lastConnectionAttempt: 0,
-        error: '',
+        error: '' as const,
       },
       isConnected: true,
       isStillReconnecting: false,
@@ -182,6 +218,8 @@ const makeConnectionProvider = socket => {
       tryReconnectNow: () => {},
       registerUserActivity: () => {},
       disconnect: () => {},
+      closeConnection: () => {},
+      getSocketDebuggingInfo: () => ({}) as SocketDebuggingInfo,
     }))
 
     return (
@@ -193,8 +231,11 @@ const makeConnectionProvider = socket => {
   return ConnectionProvider
 }
 
-const makeIdeReactProvider = (scope, socket) => {
-  const IdeReactProvider = ({ children }) => {
+const makeIdeReactProvider = (
+  scope: Record<string, unknown>,
+  socket: Socket
+) => {
+  const IdeReactProvider: FC<PropsWithChildren> = ({ children }) => {
     const [startedFreeTrial, setStartedFreeTrial] = useState(false)
 
     const [ideReactContextValue] = useState(() => ({
@@ -204,7 +245,9 @@ const makeIdeReactProvider = (scope, socket) => {
       setStartedFreeTrial,
       reportError: () => {},
       projectJoined: true,
-      permissionsLevel: scope.permissionsLevel,
+      permissionsLevel: scope.permissionsLevel as PermissionsLevel,
+      setPermissionsLevel: () => {},
+      setOutOfSync: () => {},
     }))
 
     const [ideContextValue] = useState(() => {
@@ -247,13 +290,15 @@ const makeIdeReactProvider = (scope, socket) => {
   return IdeReactProvider
 }
 
-export function makeEditorOpenDocProvider(initialValues) {
+export function makeEditorOpenDocProvider(
+  initialValues: EditorOpenDocContextState
+) {
   const {
     currentDocumentId: initialCurrentDocumentId,
     openDocName: initialOpenDocName,
     currentDocument: initialCurrentDocument,
   } = initialValues
-  const EditorOpenDocProvider = ({ children }) => {
+  const EditorOpenDocProvider: FC<PropsWithChildren> = ({ children }) => {
     const [currentDocumentId, setCurrentDocumentId] = useExposedState(
       initialCurrentDocumentId,
       'editor.open_doc_id'
@@ -285,13 +330,15 @@ export function makeEditorOpenDocProvider(initialValues) {
   return EditorOpenDocProvider
 }
 
-const makeLayoutProvider = layoutContextOverrides => {
+const makeLayoutProvider = (
+  layoutContextOverrides?: Partial<LayoutContextValue>
+) => {
   const layout = {
     ...layoutContextDefault,
     ...layoutContextOverrides,
   }
-  const LayoutProvider = ({ children }) => {
-    const [view, setView] = useState(layout.view)
+  const LayoutProvider: FC<PropsWithChildren> = ({ children }) => {
+    const [view, setView] = useState<IdeView | null>(layout.view)
     const [openFile, setOpenFile] = useState(layout.openFile)
     const [chatIsOpen, setChatIsOpen] = useState(layout.chatIsOpen)
     const [reviewPanelOpen, setReviewPanelOpen] = useState(
@@ -316,7 +363,7 @@ const makeLayoutProvider = layoutContextOverrides => {
       }, [setReviewPanelOpen])
     )
     const changeLayout = useCallback(
-      (newLayout, newView = 'editor') => {
+      (newLayout: IdeLayout, newView: IdeView = 'editor') => {
         setPdfLayout(newLayout)
         setView(newLayout === 'sideBySide' ? 'editor' : newView)
       },

+ 0 - 20
services/web/test/frontend/helpers/render-with-context.jsx

@@ -1,20 +0,0 @@
-// Disable prop type checks for test harnesses
-/* eslint-disable react/prop-types */
-
-import { render } from '@testing-library/react'
-import { EditorProviders } from './editor-providers'
-
-export function renderWithEditorContext(
-  component,
-  contextProps,
-  renderOptions = {}
-) {
-  const EditorProvidersWrapper = ({ children }) => (
-    <EditorProviders {...contextProps}>{children}</EditorProviders>
-  )
-
-  return render(component, {
-    wrapper: EditorProvidersWrapper,
-    ...renderOptions,
-  })
-}

+ 20 - 0
services/web/test/frontend/helpers/render-with-context.tsx

@@ -0,0 +1,20 @@
+import React from 'react'
+import { render, type RenderOptions } from '@testing-library/react'
+import { EditorProviders, type EditorProvidersProps } from './editor-providers'
+
+export function renderWithEditorContext(
+  component: React.ReactElement,
+  contextProps: EditorProvidersProps = {},
+  renderOptions: RenderOptions = {}
+) {
+  const EditorProvidersWrapper = ({
+    children,
+  }: {
+    children: React.ReactNode
+  }) => <EditorProviders {...contextProps}>{children}</EditorProviders>
+
+  return render(component, {
+    wrapper: EditorProvidersWrapper,
+    ...renderOptions,
+  })
+}