Procházet zdrojové kódy

Merge pull request #18222 from overleaf/jpa-socket-io-mock

[web] replace socket.io-mock with simple EventEmitter based interface

GitOrigin-RevId: 012e4263367a83e16efea3947f310a2de9a65279
Jakob Ackermann před 2 roky
rodič
revize
9c5eb9630b

+ 0 - 20
package-lock.json

@@ -36399,15 +36399,6 @@
         "ultron": "1.0.x"
       }
     },
-    "node_modules/socket.io-mock": {
-      "version": "1.3.2",
-      "resolved": "https://registry.npmjs.org/socket.io-mock/-/socket.io-mock-1.3.2.tgz",
-      "integrity": "sha512-p4MQBue3NAR8bXIHynRJxK/C+J3I3NpnnpgjptgLFSWv4u9Bdkubf2t0GCmyLmUTi03up0Cx/hQwzQfOpD187g==",
-      "dev": true,
-      "dependencies": {
-        "component-emitter": "^1.3.0"
-      }
-    },
     "node_modules/socket.io/node_modules/redis": {
       "version": "0.7.3",
       "resolved": "https://registry.npmjs.org/redis/-/redis-0.7.3.tgz",
@@ -44209,7 +44200,6 @@
         "sinon": "^7.5.0",
         "sinon-chai": "^3.7.0",
         "sinon-mongoose": "^2.3.0",
-        "socket.io-mock": "^1.3.1",
         "storybook": "^8.0.4",
         "stylelint-config-standard-scss": "^13.1.0",
         "terser-webpack-plugin": "^5.3.9",
@@ -52659,7 +52649,6 @@
         "sinon": "^7.5.0",
         "sinon-chai": "^3.7.0",
         "sinon-mongoose": "^2.3.0",
-        "socket.io-mock": "^1.3.1",
         "storybook": "^8.0.4",
         "stylelint-config-standard-scss": "^13.1.0",
         "terser-webpack-plugin": "^5.3.9",
@@ -75993,15 +75982,6 @@
         }
       }
     },
-    "socket.io-mock": {
-      "version": "1.3.2",
-      "resolved": "https://registry.npmjs.org/socket.io-mock/-/socket.io-mock-1.3.2.tgz",
-      "integrity": "sha512-p4MQBue3NAR8bXIHynRJxK/C+J3I3NpnnpgjptgLFSWv4u9Bdkubf2t0GCmyLmUTi03up0Cx/hQwzQfOpD187g==",
-      "dev": true,
-      "requires": {
-        "component-emitter": "^1.3.0"
-      }
-    },
     "sockjs": {
       "version": "0.3.24",
       "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz",

+ 20 - 0
services/web/frontend/js/ide/connection/SocketIoShim.js

@@ -1,6 +1,7 @@
 /* global io */
 
 import { debugConsole } from '@/utils/debugging'
+import EventEmitter from '@/utils/EventEmitter'
 
 class SocketShimBase {
   static connect(url, options) {
@@ -218,6 +219,25 @@ if (typeof io === 'undefined' || !io) {
   current = SocketShimV2
 }
 
+export class SocketIOMock extends EventEmitter {
+  addListener(event, listener) {
+    this.on(event, listener)
+  }
+
+  removeListener(event, listener) {
+    this.off(event, listener)
+  }
+
+  disconnect() {
+    this.emitToClient('disconnect')
+  }
+
+  emitToClient(...args) {
+    // Round-trip through JSON.parse/stringify to simulate (de-)serializing on network layer.
+    this.emit(...JSON.parse(JSON.stringify(args)))
+  }
+}
+
 export default {
   SocketShimNoop,
   SocketShimV0,

+ 20 - 34
services/web/frontend/js/utils/EventEmitter.js

@@ -1,18 +1,3 @@
-/* eslint-disable
-    camelcase,
-    max-len,
-    no-return-assign,
-    no-unused-vars,
-*/
-// TODO: This file was created by bulk-decaffeinate.
-// Fix any style issues and re-enable lint.
-/*
- * decaffeinate suggestions:
- * DS101: Remove unnecessary use of Array.from
- * DS102: Remove unnecessary code created because of implicit returns
- * DS207: Consider shorter variations of null checks
- * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
- */
 // Simple event emitter implementation, but has a slightly unusual API for
 // removing specific listeners. If a specific listener needs to be removed
 // (instead of all listeners), then it needs to use a "namespace":
@@ -23,43 +8,44 @@
 
 export default class EventEmitter {
   on(event, callback) {
-    let namespace
     if (!this.events) {
       this.events = {}
     }
+    let namespace
     ;[event, namespace] = Array.from(event.split('.'))
     if (!this.events[event]) {
       this.events[event] = []
     }
-    return this.events[event].push({
+    this.events[event].push({
       callback,
       namespace,
     })
   }
 
-  off(event) {
+  off(event, cb) {
     if (!this.events) {
       this.events = {}
     }
-    if (event != null) {
+    if (event) {
       let namespace
-      ;[event, namespace] = Array.from(event.split('.'))
-      if (namespace == null) {
+      ;[event, namespace] = event.split('.')
+      if (!this.events[event]) {
+        this.events[event] = []
+      }
+      if (cb) {
+        this.events[event] = this.events[event].filter(e => e.callback !== cb)
+      } else if (!namespace) {
         // Clear all listeners for event
-        return delete this.events[event]
+        delete this.events[event]
       } else {
         // Clear only namespaced listeners
-        const remaining_events = []
-        for (const callback of Array.from(this.events[event] || [])) {
-          if (callback.namespace !== namespace) {
-            remaining_events.push(callback)
-          }
-        }
-        return (this.events[event] = remaining_events)
+        this.events[event] = this.events[event].filter(
+          e => e.namespace !== namespace
+        )
       }
     } else {
       // Remove all listeners
-      return (this.events = {})
+      this.events = {}
     }
   }
 
@@ -67,12 +53,12 @@ export default class EventEmitter {
     if (!this.events) {
       this.events = {}
     }
-    return Array.from(this.events[event] || []).map(callback =>
-      callback.callback(...Array.from(args || []))
-    )
+    if (this.events[event]) {
+      this.events[event].forEach(e => e.callback(...args))
+    }
   }
 
   emit(...args) {
-    return this.trigger(...Array.from(args || []))
+    this.trigger(...args)
   }
 }

+ 2 - 4
services/web/frontend/stories/decorators/scope.tsx

@@ -24,6 +24,7 @@ import { FileTreePathProvider } from '@/features/file-tree/contexts/file-tree-pa
 import { UserSettingsProvider } from '@/shared/context/user-settings-context'
 import { OutlineProvider } from '@/features/ide-react/context/outline-context'
 import { ChatProvider } from '@/features/chat/context/chat-context'
+import { SocketIOMock } from '@/ide/connection/SocketIoShim'
 
 const scopeWatchers: [string, (value: any) => void][] = []
 
@@ -105,10 +106,7 @@ const initialize = () => {
 
   const ide = {
     $scope: scope,
-    socket: {
-      on: () => {},
-      removeListener: () => {},
-    },
+    socket: new SocketIOMock(),
     editorManager: {
       getCurrentDocId: () => 'foo',
       openDoc: (id: string, options: unknown) => {

+ 10 - 25
services/web/frontend/stories/file-tree.stories.jsx

@@ -1,5 +1,3 @@
-import MockedSocket from 'socket.io-mock'
-
 import { rootFolderBase } from './fixtures/file-tree-base'
 import { rootFolderLimit } from './fixtures/file-tree-limit'
 import FileTreeRoot from '../js/features/file-tree/components/file-tree-root'
@@ -7,13 +5,10 @@ import FileTreeError from '../js/features/file-tree/components/file-tree-error'
 import useFetchMock from './hooks/use-fetch-mock'
 import { ScopeDecorator } from './decorators/scope'
 import { useScope } from './hooks/use-scope'
+import { useIdeContext } from '@/shared/context/ide-context'
 
 const MOCK_DELAY = 2000
 
-window._ide = {
-  ...window._ide,
-  socket: new MockedSocket(),
-}
 const DEFAULT_PROJECT = {
   _id: '123abc',
   name: 'Some Project',
@@ -21,18 +16,14 @@ const DEFAULT_PROJECT = {
   rootFolder: rootFolderBase,
 }
 
-function defaultSetupMocks(fetchMock) {
+function defaultSetupMocks(fetchMock, socket) {
   fetchMock
     .post(
       /\/project\/\w+\/(file|doc|folder)\/\w+\/rename/,
       (path, req) => {
         const body = JSON.parse(req.body)
         const entityId = path.match(/([^/]+)\/rename$/)[1]
-        window._ide.socket.socketClient.emit(
-          'reciveEntityRename',
-          entityId,
-          body.name
-        )
+        socket.emitToClient('reciveEntityRename', entityId, body.name)
         return 204
       },
 
@@ -51,11 +42,7 @@ function defaultSetupMocks(fetchMock) {
           _id: Math.random().toString(16).replace(/0\./, 'random-test-id-'),
           name: body.name,
         }
-        window._ide.socket.socketClient.emit(
-          'reciveNewFolder',
-          body.parent_folder_id,
-          newFolder
-        )
+        socket.emitToClient('reciveNewFolder', body.parent_folder_id, newFolder)
         return newFolder
       },
       {
@@ -66,7 +53,7 @@ function defaultSetupMocks(fetchMock) {
       /\/project\/\w+\/(file|doc|folder)\/\w+/,
       path => {
         const entityId = path.match(/[^/]+$/)[0]
-        window._ide.socket.socketClient.emit('removeEntity', entityId)
+        socket.emitToClient('removeEntity', entityId)
         return 204
       },
       {
@@ -76,17 +63,14 @@ function defaultSetupMocks(fetchMock) {
     .post(/\/project\/\w+\/(file|doc|folder)\/\w+\/move/, (path, req) => {
       const body = JSON.parse(req.body)
       const entityId = path.match(/([^/]+)\/move/)[1]
-      window._ide.socket.socketClient.emit(
-        'reciveEntityMove',
-        entityId,
-        body.folder_id
-      )
+      socket.emitToClient('reciveEntityMove', entityId, body.folder_id)
       return 204
     })
 }
 
 export const FullTree = args => {
-  useFetchMock(defaultSetupMocks)
+  const { socket } = useIdeContext()
+  useFetchMock(fetchMock => defaultSetupMocks(fetchMock, socket))
 
   useScope({
     project: DEFAULT_PROJECT,
@@ -149,7 +133,8 @@ export const FallbackError = args => {
 }
 
 export const FilesLimit = args => {
-  useFetchMock(defaultSetupMocks)
+  const { socket } = useIdeContext()
+  useFetchMock(fetchMock => defaultSetupMocks(fetchMock, socket))
 
   useScope({
     project: {

+ 0 - 1
services/web/package.json

@@ -347,7 +347,6 @@
     "sinon": "^7.5.0",
     "sinon-chai": "^3.7.0",
     "sinon-mongoose": "^2.3.0",
-    "socket.io-mock": "^1.3.1",
     "storybook": "^8.0.4",
     "stylelint-config-standard-scss": "^13.1.0",
     "terser-webpack-plugin": "^5.3.9",

+ 22 - 22
services/web/test/frontend/features/chat/context/chat-context.test.jsx

@@ -5,7 +5,6 @@ import { renderHook, act } from '@testing-library/react-hooks/dom'
 import { expect } from 'chai'
 import sinon from 'sinon'
 import fetchMock from 'fetch-mock'
-import EventEmitter from 'events'
 import {
   useChatContext,
   chatClientIdGenerator,
@@ -15,6 +14,7 @@ import {
   cleanUpContext,
 } from '../../../helpers/render-with-context'
 import { stubMathJax, tearDownMathJaxStubs } from '../components/stubs'
+import { SocketIOMock } from '@/ide/connection/SocketIoShim'
 
 describe('ChatContext', function () {
   const user = {
@@ -57,26 +57,26 @@ describe('ChatContext', function () {
     })
 
     it('subscribes when mounted', function () {
-      const socket = new EventEmitter()
+      const socket = new SocketIOMock()
       renderChatContextHook({ socket })
 
       // Assert that there is 1 listener
-      expect(socket.rawListeners('new-chat-message').length).to.equal(1)
+      expect(socket.events['new-chat-message'].length).to.equal(1)
     })
 
     it('unsubscribes when unmounted', function () {
-      const socket = new EventEmitter()
+      const socket = new SocketIOMock()
       const { unmount } = renderChatContextHook({ socket })
 
       unmount()
 
       // Assert that there is 0 listeners
-      expect(socket.rawListeners('new-chat-message').length).to.equal(0)
+      expect(socket.events['new-chat-message'].length).to.equal(0)
     })
 
     it('adds received messages to the list', async function () {
       // Mock socket: we only need to emit events, not mock actual connections
-      const socket = new EventEmitter()
+      const socket = new SocketIOMock()
       const { result, waitForNextUpdate } = renderChatContextHook({
         socket,
       })
@@ -89,7 +89,7 @@ describe('ChatContext', function () {
       expect(result.current.messages).to.deep.equal([])
 
       // Mock message being received from another user
-      socket.emit('new-chat-message', {
+      socket.emitToClient('new-chat-message', {
         id: 'msg_1',
         content: 'new message',
         timestamp: Date.now(),
@@ -107,7 +107,7 @@ describe('ChatContext', function () {
 
     it('deduplicate messages from preloading', async function () {
       // Mock socket: we only need to emit events, not mock actual connections
-      const socket = new EventEmitter()
+      const socket = new SocketIOMock()
       const { result, waitForNextUpdate } = renderChatContextHook({
         socket,
       })
@@ -130,7 +130,7 @@ describe('ChatContext', function () {
       )
 
       // Mock message being received from another user
-      socket.emit('new-chat-message', {
+      socket.emitToClient('new-chat-message', {
         id: 'msg_1',
         content: 'new message',
         timestamp: Date.now(),
@@ -158,7 +158,7 @@ describe('ChatContext', function () {
 
     it('deduplicate messages from websocket', async function () {
       // Mock socket: we only need to emit events, not mock actual connections
-      const socket = new EventEmitter()
+      const socket = new SocketIOMock()
       const { result, waitForNextUpdate } = renderChatContextHook({
         socket,
       })
@@ -188,7 +188,7 @@ describe('ChatContext', function () {
       expect(result.current.messages).to.have.length(1)
 
       // Mock message being received from another user
-      socket.emit('new-chat-message', {
+      socket.emitToClient('new-chat-message', {
         id: 'msg_1',
         content: 'new message',
         timestamp: Date.now(),
@@ -208,7 +208,7 @@ describe('ChatContext', function () {
     })
 
     it("doesn't add received messages from the current user if a message was just sent", async function () {
-      const socket = new EventEmitter()
+      const socket = new SocketIOMock()
       const { result, waitForNextUpdate } = renderChatContextHook({
         socket,
       })
@@ -223,7 +223,7 @@ describe('ChatContext', function () {
 
       act(() => {
         // Receive a message from the current user
-        socket.emit('new-chat-message', {
+        socket.emitToClient('new-chat-message', {
           id: 'msg_1',
           content: 'received message',
           timestamp: Date.now(),
@@ -240,7 +240,7 @@ describe('ChatContext', function () {
     })
 
     it('adds the new message from the current user if another message was received after sending', async function () {
-      const socket = new EventEmitter()
+      const socket = new SocketIOMock()
       const { result, waitForNextUpdate } = renderChatContextHook({
         socket,
       })
@@ -260,7 +260,7 @@ describe('ChatContext', function () {
 
       act(() => {
         // Receive a message from another user.
-        socket.emit('new-chat-message', {
+        socket.emitToClient('new-chat-message', {
           id: 'msg_1',
           content: otherMsg,
           timestamp: Date.now(),
@@ -278,7 +278,7 @@ describe('ChatContext', function () {
 
       act(() => {
         // Receive a message from the current user
-        socket.emit('new-chat-message', {
+        socket.emitToClient('new-chat-message', {
           id: 'msg_2',
           content: 'received message from current user',
           timestamp: Date.now(),
@@ -441,7 +441,7 @@ describe('ChatContext', function () {
         })
       )
 
-      const socket = new EventEmitter()
+      const socket = new SocketIOMock()
       const { result, waitForNextUpdate } = renderChatContextHook({
         socket,
       })
@@ -451,7 +451,7 @@ describe('ChatContext', function () {
 
       // Mock message being received from the socket while the request is in
       // flight
-      socket.emit('new-chat-message', {
+      socket.emitToClient('new-chat-message', {
         id: 'socket_msg',
         content: 'socket message',
         timestamp: Date.now(),
@@ -563,11 +563,11 @@ describe('ChatContext', function () {
     })
 
     it('increments unreadMessageCount when a new message is received', function () {
-      const socket = new EventEmitter()
+      const socket = new SocketIOMock()
       const { result } = renderChatContextHook({ socket })
 
       // Receive a new message from the socket
-      socket.emit('new-chat-message', {
+      socket.emitToClient('new-chat-message', {
         id: 'msg_1',
         content: 'new message',
         timestamp: Date.now(),
@@ -578,12 +578,12 @@ describe('ChatContext', function () {
     })
 
     it('resets unreadMessageCount when markMessagesAsRead is called', function () {
-      const socket = new EventEmitter()
+      const socket = new SocketIOMock()
       const { result } = renderChatContextHook({ socket })
 
       // Receive a new message from the socket, incrementing unreadMessageCount
       // by 1
-      socket.emit('new-chat-message', {
+      socket.emitToClient('new-chat-message', {
         id: 'msg_1',
         content: 'new message',
         timestamp: Date.now(),

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

@@ -1,7 +1,6 @@
-// @ts-ignore
-import MockedSocket from 'socket.io-mock'
 import FileTreeRoot from '../../../../../frontend/js/features/file-tree/components/file-tree-root'
 import { EditorProviders } from '../../../helpers/editor-providers'
+import { SocketIOMock } from '@/ide/connection/SocketIoShim'
 
 describe('<FileTreeRoot/>', function () {
   beforeEach(function () {
@@ -287,7 +286,9 @@ describe('<FileTreeRoot/>', function () {
   })
 
   describe('when deselecting files', function () {
+    let socket: SocketIOMock
     beforeEach(function () {
+      socket = new SocketIOMock()
       const rootFolder = [
         {
           _id: 'root-folder-id',
@@ -313,7 +314,7 @@ describe('<FileTreeRoot/>', function () {
           rootDocId="456def"
           features={{} as any}
           permissionsLevel="owner"
-          socket={new MockedSocket()}
+          socket={socket}
         >
           <FileTreeRoot
             refProviders={{}}
@@ -356,9 +357,8 @@ describe('<FileTreeRoot/>', function () {
       cy.findByRole('button', { name: /new file/i }).click()
       cy.findByRole('button', { name: /create/i }).click()
 
-      cy.window().then(win => {
-        // @ts-ignore
-        win._ide.socket.socketClient.emit('reciveNewDoc', 'root-folder-id', {
+      cy.then(() => {
+        socket.emitToClient('reciveNewDoc', 'root-folder-id', {
           _id: '12345',
           name: 'abcdef.tex',
           docs: [],

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

@@ -1,10 +1,11 @@
-// @ts-ignore
-import MockedSocket from 'socket.io-mock'
 import FileTreeRoot from '../../../../../frontend/js/features/file-tree/components/file-tree-root'
 import { EditorProviders } from '../../../helpers/editor-providers'
+import { SocketIOMock } from '@/ide/connection/SocketIoShim'
 
 describe('FileTree Create Folder Flow', function () {
+  let socket: SocketIOMock
   beforeEach(function () {
+    socket = new SocketIOMock()
     cy.window().then(win => {
       win.metaAttributesCache.set('ol-user', { id: 'user1' })
     })
@@ -25,7 +26,7 @@ describe('FileTree Create Folder Flow', function () {
       <EditorProviders
         rootFolder={rootFolder as any}
         projectId="123abc"
-        socket={new MockedSocket()}
+        socket={socket}
       >
         <FileTreeRoot
           refProviders={{}}
@@ -58,9 +59,8 @@ describe('FileTree Create Folder Flow', function () {
       name,
     })
 
-    cy.window().then(win => {
-      // @ts-ignore
-      win._ide.socket.socketClient.emit('reciveNewFolder', 'root-folder-id', {
+    cy.then(() => {
+      socket.emitToClient('reciveNewFolder', 'root-folder-id', {
         _id: fakeId(),
         name,
         docs: [],
@@ -96,7 +96,7 @@ describe('FileTree Create Folder Flow', function () {
         rootFolder={rootFolder as any}
         projectId="123abc"
         rootDocId="789ghi"
-        socket={new MockedSocket()}
+        socket={socket}
       >
         <FileTreeRoot
           refProviders={{}}
@@ -131,9 +131,8 @@ describe('FileTree Create Folder Flow', function () {
       name,
     })
 
-    cy.window().then(win => {
-      // @ts-ignore
-      win._ide.socket.socketClient.emit('reciveNewFolder', '789ghi', {
+    cy.then(() => {
+      socket.emitToClient('reciveNewFolder', '789ghi', {
         _id: fakeId(),
         name,
         docs: [],
@@ -174,7 +173,7 @@ describe('FileTree Create Folder Flow', function () {
         rootFolder={rootFolder as any}
         projectId="123abc"
         rootDocId="456def"
-        socket={new MockedSocket()}
+        socket={socket}
       >
         <FileTreeRoot
           refProviders={{}}
@@ -207,9 +206,8 @@ describe('FileTree Create Folder Flow', function () {
       name,
     })
 
-    cy.window().then(win => {
-      // @ts-ignore
-      win._ide.socket.socketClient.emit('reciveNewFolder', '789ghi', {
+    cy.then(() => {
+      socket.emitToClient('reciveNewFolder', '789ghi', {
         _id: fakeId(),
         name,
         docs: [],
@@ -242,7 +240,7 @@ describe('FileTree Create Folder Flow', function () {
         rootFolder={rootFolder as any}
         projectId="123abc"
         rootDocId="456def"
-        socket={new MockedSocket()}
+        socket={socket}
       >
         <FileTreeRoot
           refProviders={{}}

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

@@ -1,7 +1,6 @@
-// @ts-ignore
-import MockedSocket from 'socket.io-mock'
 import FileTreeRoot from '../../../../../frontend/js/features/file-tree/components/file-tree-root'
 import { EditorProviders } from '../../../helpers/editor-providers'
+import { SocketIOMock } from '@/ide/connection/SocketIoShim'
 
 describe('FileTree Delete Entity Flow', function () {
   beforeEach(function () {
@@ -11,7 +10,9 @@ describe('FileTree Delete Entity Flow', function () {
   })
 
   describe('single entity', function () {
+    let socket: SocketIOMock
     beforeEach(function () {
+      socket = new SocketIOMock()
       const rootFolder = [
         {
           _id: 'root-folder-id',
@@ -30,7 +31,7 @@ describe('FileTree Delete Entity Flow', function () {
           <EditorProviders
             rootFolder={rootFolder as any}
             projectId="123abc"
-            socket={new MockedSocket()}
+            socket={socket}
           >
             <FileTreeRoot
               refProviders={{}}
@@ -64,9 +65,8 @@ describe('FileTree Delete Entity Flow', function () {
 
       cy.wait('@deleteDoc')
 
-      cy.window().then(win => {
-        // @ts-ignore
-        win._ide.socket.socketClient.emit('removeEntity', '456def')
+      cy.then(() => {
+        socket.emitToClient('removeEntity', '456def')
       })
 
       cy.findByRole('treeitem', {
@@ -99,9 +99,8 @@ describe('FileTree Delete Entity Flow', function () {
 
       cy.findByRole('button', { name: 'Delete' }).click()
 
-      cy.window().then(win => {
-        // @ts-ignore
-        win._ide.socket.socketClient.emit('removeEntity', '456def')
+      cy.then(() => {
+        socket.emitToClient('removeEntity', '456def')
       })
 
       cy.findByRole('treeitem', {
@@ -133,7 +132,9 @@ describe('FileTree Delete Entity Flow', function () {
   })
 
   describe('folders', function () {
+    let socket: SocketIOMock
     beforeEach(function () {
+      socket = new SocketIOMock()
       const rootFolder = [
         {
           _id: 'root-folder-id',
@@ -157,7 +158,7 @@ describe('FileTree Delete Entity Flow', function () {
           <EditorProviders
             rootFolder={rootFolder as any}
             projectId="123abc"
-            socket={new MockedSocket()}
+            socket={socket}
           >
             <FileTreeRoot
               refProviders={{}}
@@ -179,9 +180,8 @@ describe('FileTree Delete Entity Flow', function () {
         cmdKey: true,
       })
 
-      cy.window().then(win => {
-        // @ts-ignore
-        win._ide.socket.socketClient.emit('removeEntity', '123abc')
+      cy.then(() => {
+        socket.emitToClient('removeEntity', '123abc')
       })
     })
 
@@ -204,7 +204,9 @@ describe('FileTree Delete Entity Flow', function () {
   })
 
   describe('multiple entities', function () {
+    let socket: SocketIOMock
     beforeEach(function () {
+      socket = new SocketIOMock()
       const rootFolder = [
         {
           _id: 'root-folder-id',
@@ -220,7 +222,7 @@ describe('FileTree Delete Entity Flow', function () {
           <EditorProviders
             rootFolder={rootFolder as any}
             projectId="123abc"
-            socket={new MockedSocket()}
+            socket={socket}
           >
             <FileTreeRoot
               refProviders={{}}
@@ -264,11 +266,9 @@ describe('FileTree Delete Entity Flow', function () {
 
       cy.findByRole('button', { name: 'Delete' }).click()
 
-      cy.window().then(win => {
-        // @ts-ignore
-        win._ide.socket.socketClient.emit('removeEntity', '456def')
-        // @ts-ignore
-        win._ide.socket.socketClient.emit('removeEntity', '789ghi')
+      cy.then(() => {
+        socket.emitToClient('removeEntity', '456def')
+        socket.emitToClient('removeEntity', '789ghi')
       })
 
       for (const name of ['main.tex', 'my.bib']) {

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

@@ -1,7 +1,6 @@
-// @ts-ignore
-import MockedSocket from 'socket.io-mock'
 import FileTreeRoot from '../../../../../frontend/js/features/file-tree/components/file-tree-root'
 import { EditorProviders } from '../../../helpers/editor-providers'
+import { SocketIOMock } from '@/ide/connection/SocketIoShim'
 
 describe('FileTree Rename Entity Flow', function () {
   beforeEach(function () {
@@ -10,7 +9,9 @@ describe('FileTree Rename Entity Flow', function () {
     })
   })
 
+  let socket: SocketIOMock
   beforeEach(function () {
+    socket = new SocketIOMock()
     const rootFolder = [
       {
         _id: 'root-folder-id',
@@ -37,7 +38,7 @@ describe('FileTree Rename Entity Flow', function () {
         <EditorProviders
           rootFolder={rootFolder as any}
           projectId="123abc"
-          socket={new MockedSocket()}
+          socket={socket}
         >
           <FileTreeRoot
             refProviders={{}}
@@ -146,13 +147,8 @@ describe('FileTree Rename Entity Flow', function () {
     it('renames doc', function () {
       cy.findByRole('treeitem', { name: 'a.tex' })
 
-      cy.window().then(win => {
-        // @ts-ignore
-        win._ide.socket.socketClient.emit(
-          'reciveEntityRename',
-          '456def',
-          'socket.tex'
-        )
+      cy.then(() => {
+        socket.emitToClient('reciveEntityRename', '456def', 'socket.tex')
       })
 
       cy.findByRole('treeitem', { name: 'socket.tex' })

+ 2 - 4
services/web/test/frontend/helpers/editor-providers.jsx

@@ -16,6 +16,7 @@ import { ProjectSettingsProvider } from '@/features/editor-left-menu/context/pro
 import { FileTreePathProvider } from '@/features/file-tree/contexts/file-tree-path'
 import { UserSettingsProvider } from '@/shared/context/user-settings-context'
 import { OutlineProvider } from '@/features/ide-react/context/outline-context'
+import { SocketIOMock } from '@/ide/connection/SocketIoShim'
 
 // these constants can be imported in tests instead of
 // using magic strings
@@ -46,10 +47,7 @@ export function EditorProviders({
     email: 'owner@example.com',
   },
   rootDocId = '_root_doc_id',
-  socket = {
-    on: sinon.stub(),
-    removeListener: sinon.stub(),
-  },
+  socket = new SocketIOMock(),
   isRestrictedTokenMember = false,
   clsiServerId = '1234',
   scope = {},