diff --git a/package-lock.json b/package-lock.json
index e14839553f..f0a02ba701 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -32072,11 +32072,6 @@
"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
"dev": true
},
- "node_modules/sysend": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/sysend/-/sysend-1.10.0.tgz",
- "integrity": "sha512-kQDpqW60fvgbNLnWnTRaJ2KGX3aW5fThu9St8T4h+j8XC1YIDXhWVqS8Ho+WYgasmtrP7RvcRRhs+SVCb9o7wA=="
- },
"node_modules/table-layout": {
"version": "0.4.5",
"resolved": "https://registry.npmjs.org/table-layout/-/table-layout-0.4.5.tgz",
@@ -38406,7 +38401,6 @@
"rolling-rate-limiter": "^0.2.10",
"sanitize-html": "^1.27.1",
"scroll-into-view-if-needed": "^2.2.25",
- "sysend": "^1.10.0",
"tsscmp": "^1.0.6",
"underscore": "^1.13.1",
"unzipper": "^0.10.11",
@@ -49247,7 +49241,6 @@
"sinon-chai": "^3.7.0",
"sinon-mongoose": "^2.3.0",
"socket.io-mock": "^1.3.1",
- "sysend": "^1.10.0",
"terser-webpack-plugin": "^5.3.1",
"timekeeper": "^2.2.0",
"to-string-loader": "^1.2.0",
@@ -70904,11 +70897,6 @@
"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
"dev": true
},
- "sysend": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/sysend/-/sysend-1.10.0.tgz",
- "integrity": "sha512-kQDpqW60fvgbNLnWnTRaJ2KGX3aW5fThu9St8T4h+j8XC1YIDXhWVqS8Ho+WYgasmtrP7RvcRRhs+SVCb9o7wA=="
- },
"table-layout": {
"version": "0.4.5",
"resolved": "https://registry.npmjs.org/table-layout/-/table-layout-0.4.5.tgz",
diff --git a/services/web/frontend/js/shared/context/detach-context.js b/services/web/frontend/js/shared/context/detach-context.js
index e9b21c9d9d..ea558fbcdf 100644
--- a/services/web/frontend/js/shared/context/detach-context.js
+++ b/services/web/frontend/js/shared/context/detach-context.js
@@ -7,7 +7,6 @@ import {
useState,
} from 'react'
import PropTypes from 'prop-types'
-import sysend from 'sysend'
import getMeta from '../../utils/meta'
import { buildUrlWithDetachRole } from '../utils/url-helper'
import useCallbackHandlers from '../hooks/use-callback-handlers'
@@ -26,7 +25,12 @@ DetachContext.Provider.propTypes = {
const debugPdfDetach = getMeta('ol-debugPdfDetach')
-const SYSEND_CHANNEL = `detach-${getMeta('ol-project_id')}`
+const projectId = getMeta('ol-project_id')
+export const detachChannelId = `detach-${projectId}`
+export const detachChannel =
+ 'BroadcastChannel' in window
+ ? new BroadcastChannel(detachChannelId)
+ : undefined
export function DetachProvider({ children }) {
const [lastDetachedConnectedAt, setLastDetachedConnectedAt] = useState()
@@ -45,13 +49,20 @@ export function DetachProvider({ children }) {
}, [role])
useEffect(() => {
- sysend.on(SYSEND_CHANNEL, message => {
- if (debugPdfDetach) {
- console.log(`Receiving:`, message)
+ if (detachChannel) {
+ const listener = event => {
+ if (debugPdfDetach) {
+ console.log(`Receiving:`, event.data)
+ }
+ callEventHandlers(event.data)
}
- callEventHandlers(message)
- })
- return () => sysend.off(SYSEND_CHANNEL)
+
+ detachChannel.addEventListener('message', listener)
+
+ return () => {
+ detachChannel.removeEventListener('message', listener)
+ }
+ }
}, [callEventHandlers])
const broadcastEvent = useCallback(
@@ -80,7 +91,8 @@ export function DetachProvider({ children }) {
if (data) {
message.data = data
}
- sysend.broadcast(SYSEND_CHANNEL, message)
+
+ detachChannel?.postMessage(message)
},
[role]
)
diff --git a/services/web/package.json b/services/web/package.json
index e7bbd60e03..9c5e1100ad 100644
--- a/services/web/package.json
+++ b/services/web/package.json
@@ -232,7 +232,6 @@
"rolling-rate-limiter": "^0.2.10",
"sanitize-html": "^1.27.1",
"scroll-into-view-if-needed": "^2.2.25",
- "sysend": "^1.10.0",
"tsscmp": "^1.0.6",
"underscore": "^1.13.1",
"unzipper": "^0.10.11",
diff --git a/services/web/test/frontend/bootstrap.js b/services/web/test/frontend/bootstrap.js
index f4344339ed..6e09370c42 100644
--- a/services/web/test/frontend/bootstrap.js
+++ b/services/web/test/frontend/bootstrap.js
@@ -98,6 +98,16 @@ globalThis.ResizeObserver =
window.ResizeObserver =
require('@juggle/resize-observer').ResizeObserver
+// add stub for BroadcastChannel (unused in these tests)
+globalThis.BroadcastChannel =
+ global.BroadcastChannel =
+ window.BroadcastChannel =
+ class BroadcastChannel {
+ addEventListener(type, listener) {}
+ removeEventListener(type, listener) {}
+ postMessage(message) {}
+ }
+
// node-fetch doesn't accept relative URL's: https://github.com/node-fetch/node-fetch/blob/master/docs/v2-LIMITS.md#known-differences
const fetch = require('node-fetch')
globalThis.fetch =
diff --git a/services/web/test/frontend/components/pdf-preview/detach-compile-button.spec.tsx b/services/web/test/frontend/components/pdf-preview/detach-compile-button.spec.tsx
index 1323657248..be8caadb34 100644
--- a/services/web/test/frontend/components/pdf-preview/detach-compile-button.spec.tsx
+++ b/services/web/test/frontend/components/pdf-preview/detach-compile-button.spec.tsx
@@ -1,7 +1,7 @@
-import sysendTestHelper from '../../helpers/sysend'
import { EditorProviders } from '../../helpers/editor-providers'
import DetachCompileButton from '../../../../frontend/js/features/pdf-preview/components/detach-compile-button'
import { mockScope } from './scope'
+import { testDetachChannel } from '../../helpers/detach-channel'
describe('', function () {
beforeEach(function () {
@@ -11,7 +11,6 @@ describe('', function () {
afterEach(function () {
window.metaAttributesCache = new Map()
- sysendTestHelper.resetHistory()
})
it('detacher mode and not linked: does not show button ', function () {
@@ -30,7 +29,7 @@ describe('', function () {
cy.findByRole('button', { name: 'Recompile' }).should('not.exist')
})
- it('detacher mode and linked: show button ', function () {
+ it('detacher mode and linked: show button', function () {
cy.window().then(win => {
win.metaAttributesCache = new Map([['ol-detachRole', 'detacher']])
})
@@ -41,8 +40,10 @@ describe('', function () {
- ).then(() => {
- sysendTestHelper.receiveMessage({
+ )
+
+ cy.wrap(null).then(() => {
+ testDetachChannel.postMessage({
role: 'detached',
event: 'connected',
})
@@ -62,8 +63,10 @@ describe('', function () {
- ).then(() => {
- sysendTestHelper.receiveMessage({
+ )
+
+ cy.wrap(null).then(() => {
+ testDetachChannel.postMessage({
role: 'detacher',
event: 'connected',
})
diff --git a/services/web/test/frontend/components/pdf-preview/pdf-logs-entries.spec.tsx b/services/web/test/frontend/components/pdf-preview/pdf-logs-entries.spec.tsx
index 669e254ccf..e42f2216ff 100644
--- a/services/web/test/frontend/components/pdf-preview/pdf-logs-entries.spec.tsx
+++ b/services/web/test/frontend/components/pdf-preview/pdf-logs-entries.spec.tsx
@@ -1,6 +1,6 @@
-import sysendTestHelper from '../../helpers/sysend'
import { EditorProviders } from '../../helpers/editor-providers'
import PdfLogsEntries from '../../../../frontend/js/features/pdf-preview/components/pdf-logs-entries'
+import { detachChannel, testDetachChannel } from '../../helpers/detach-channel'
window.metaAttributesCache = new Map([['ol-debugPdfDetach', true]])
describe('', function () {
@@ -38,7 +38,6 @@ describe('', function () {
afterEach(function () {
window.metaAttributesCache = new Map()
- sysendTestHelper.resetHistory()
})
it('displays human readable hint', function () {
@@ -60,16 +59,13 @@ describe('', function () {
cy.findByRole('button', {
name: 'Navigate to log position in source code: main.tex, 9',
+ }).click()
+
+ cy.get('@findEntityByPath').should('be.calledOnce')
+ cy.get('@openDoc').should('be.calledOnceWith', fakeEntity, {
+ gotoLine: 9,
+ gotoColumn: 8,
})
- .click()
- .then(() => {
- expect(props.fileTreeManager.findEntityByPath).to.be.calledOnce
- expect(props.editorManager.openDoc).to.be.calledOnce
- expect(props.editorManager.openDoc).to.be.calledWith(fakeEntity, {
- gotoLine: 9,
- gotoColumn: 8,
- })
- })
})
it('opens doc via detached action', function () {
@@ -82,7 +78,7 @@ describe('', function () {
).then(() => {
- sysendTestHelper.receiveMessage({
+ testDetachChannel.postMessage({
role: 'detached',
event: 'action-sync-to-entry',
data: {
@@ -95,13 +91,12 @@ describe('', function () {
],
},
})
+ })
- expect(props.fileTreeManager.findEntityByPath).to.be.calledOnce
- expect(props.editorManager.openDoc).to.be.calledOnce
- expect(props.editorManager.openDoc).to.be.calledWith(fakeEntity, {
- gotoLine: 7,
- gotoColumn: 6,
- })
+ cy.get('@findEntityByPath').should('be.calledOnce')
+ cy.get('@openDoc').should('be.calledOnceWith', fakeEntity, {
+ gotoLine: 7,
+ gotoColumn: 6,
})
})
@@ -116,27 +111,26 @@ describe('', function () {
)
+ cy.spy(detachChannel, 'postMessage').as('postDetachMessage')
+
cy.findByRole('button', {
name: 'Navigate to log position in source code: main.tex, 9',
- })
- .click()
- .then(() => {
- expect(props.fileTreeManager.findEntityByPath).not.to.be.called
- expect(props.editorManager.openDoc).not.to.be.called
+ }).click()
- expect(sysendTestHelper.getLastBroacastMessage()).to.deep.equal({
- role: 'detached',
- event: 'action-sync-to-entry',
- data: {
- args: [
- {
- file: 'main.tex',
- line: 9,
- column: 8,
- },
- ],
+ cy.get('@findEntityByPath').should('not.be.called')
+ cy.get('@openDoc').should('not.be.called')
+ cy.get('@postDetachMessage').should('be.calledWith', {
+ role: 'detached',
+ event: 'action-sync-to-entry',
+ data: {
+ args: [
+ {
+ file: 'main.tex',
+ line: 9,
+ column: 8,
},
- })
- })
+ ],
+ },
+ })
})
})
diff --git a/services/web/test/frontend/components/pdf-preview/pdf-preview-detached-root.spec.tsx b/services/web/test/frontend/components/pdf-preview/pdf-preview-detached-root.spec.tsx
index ee760ff24f..007fa48d76 100644
--- a/services/web/test/frontend/components/pdf-preview/pdf-preview-detached-root.spec.tsx
+++ b/services/web/test/frontend/components/pdf-preview/pdf-preview-detached-root.spec.tsx
@@ -1,10 +1,8 @@
-import sysendTestHelper from '../../helpers/sysend'
import PdfPreviewDetachedRoot from '../../../../frontend/js/features/pdf-preview/components/pdf-preview-detached-root'
import { User } from '../../../../types/user'
+import { detachChannel, testDetachChannel } from '../../helpers/detach-channel'
-// https://github.com/overleaf/internal/issues/10080
-// eslint-disable-next-line mocha/no-skipped-tests
-describe.skip('', function () {
+describe('', function () {
beforeEach(function () {
window.user = { id: 'user1' } as User
@@ -21,17 +19,18 @@ describe.skip('', function () {
afterEach(function () {
window.metaAttributesCache = new Map()
- sysendTestHelper.resetHistory()
})
it('syncs compiling state', function () {
- cy.mount().then(() => {
- sysendTestHelper.receiveMessage({
+ cy.mount()
+
+ cy.wrap(null).then(() => {
+ testDetachChannel.postMessage({
role: 'detacher',
event: 'connected',
})
- sysendTestHelper.receiveMessage({
+ testDetachChannel.postMessage({
role: 'detacher',
event: 'state-compiling',
data: { value: true },
@@ -39,39 +38,41 @@ describe.skip('', function () {
})
cy.findByRole('button', { name: 'Compiling…' })
- cy.findByRole('button', { name: 'Recompile' })
- .should('not.exist')
- .then(() => {
- sysendTestHelper.receiveMessage({
- role: 'detacher',
- event: 'state-compiling',
- data: { value: false },
- })
+ cy.findByRole('button', { name: 'Recompile' }).should('not.exist')
+ cy.wrap(null).then(() => {
+ testDetachChannel.postMessage({
+ role: 'detacher',
+ event: 'state-compiling',
+ data: { value: false },
})
+ })
cy.findByRole('button', { name: 'Recompile' })
cy.findByRole('button', { name: 'Compiling…' }).should('not.exist')
})
it('sends a clear cache request when the button is pressed', function () {
- cy.mount().then(() => {
- sysendTestHelper.receiveMessage({
+ cy.mount()
+
+ cy.wrap(null).then(() => {
+ testDetachChannel.postMessage({
role: 'detacher',
event: 'state-showLogs',
data: { value: true },
})
})
+ cy.spy(detachChannel, 'postMessage').as('postDetachMessage')
+
cy.findByRole('button', { name: 'Clear cached files' })
.should('not.be.disabled')
.click()
- .should(() => {
- expect(sysendTestHelper.getLastBroacastMessage()).to.deep.equal({
- role: 'detached',
- event: 'action-clearCache',
- data: {
- args: [],
- },
- })
- })
+
+ cy.get('@postDetachMessage').should('be.calledWith', {
+ role: 'detached',
+ event: 'action-clearCache',
+ data: {
+ args: [],
+ },
+ })
})
})
diff --git a/services/web/test/frontend/components/pdf-preview/pdf-preview-hybrid-toolbar.spec.tsx b/services/web/test/frontend/components/pdf-preview/pdf-preview-hybrid-toolbar.spec.tsx
index a110e34029..c88c9e219a 100644
--- a/services/web/test/frontend/components/pdf-preview/pdf-preview-hybrid-toolbar.spec.tsx
+++ b/services/web/test/frontend/components/pdf-preview/pdf-preview-hybrid-toolbar.spec.tsx
@@ -1,6 +1,6 @@
-import sysendTestHelper from '../../helpers/sysend'
import { EditorProviders } from '../../helpers/editor-providers'
import PdfPreviewHybridToolbar from '../../../../frontend/js/features/pdf-preview/components/pdf-preview-hybrid-toolbar'
+import { testDetachChannel } from '../../helpers/detach-channel'
describe('', function () {
beforeEach(function () {
@@ -10,7 +10,6 @@ describe('', function () {
afterEach(function () {
window.metaAttributesCache = new Map()
- sysendTestHelper.resetHistory()
})
it('shows normal mode', function () {
@@ -47,8 +46,10 @@ describe('', function () {
- ).then(() => {
- sysendTestHelper.receiveMessage({
+ )
+
+ cy.wrap(null).then(() => {
+ testDetachChannel.postMessage({
role: 'detacher',
event: 'connected',
})
@@ -66,12 +67,14 @@ describe('', function () {
- ).then(() => {
- sysendTestHelper.receiveMessage({
+ )
+
+ cy.wrap(null).then(() => {
+ testDetachChannel.postMessage({
role: 'detacher',
event: 'connected',
})
- sysendTestHelper.receiveMessage({
+ testDetachChannel.postMessage({
role: 'detacher',
event: 'closed',
})
diff --git a/services/web/test/frontend/components/pdf-preview/pdf-synctex-controls.spec.tsx b/services/web/test/frontend/components/pdf-preview/pdf-synctex-controls.spec.tsx
index 759885bb8e..e340ca1092 100644
--- a/services/web/test/frontend/components/pdf-preview/pdf-synctex-controls.spec.tsx
+++ b/services/web/test/frontend/components/pdf-preview/pdf-synctex-controls.spec.tsx
@@ -1,11 +1,11 @@
import PdfSynctexControls from '../../../../frontend/js/features/pdf-preview/components/pdf-synctex-controls'
-import sysendTestHelper from '../../helpers/sysend'
import { cloneDeep } from 'lodash'
import { useDetachCompileContext as useCompileContext } from '../../../../frontend/js/shared/context/detach-compile-context'
import { useFileTreeData } from '../../../../frontend/js/shared/context/file-tree-data-context'
import { useEffect } from 'react'
import { EditorProviders } from '../../helpers/editor-providers'
import { mockScope } from './scope'
+import { detachChannel, testDetachChannel } from '../../helpers/detach-channel'
const mockHighlights = [
{
@@ -55,7 +55,7 @@ const WithPosition = ({ mockPosition }: { mockPosition: Position }) => {
// mock PDF scroll position update
const setDetachedPosition = (mockPosition: Position) => {
- sysendTestHelper.receiveMessage({
+ testDetachChannel.postMessage({
role: 'detacher',
event: 'state-position',
data: { value: mockPosition },
@@ -127,8 +127,7 @@ const interceptSyncPdf = () => {
}).as('sync-pdf')
}
-// eslint-disable-next-line mocha/no-skipped-tests
-describe.skip('', function () {
+describe('', function () {
beforeEach(function () {
window.metaAttributesCache = new Map()
@@ -255,9 +254,7 @@ describe.skip('', function () {
- ).then(() => {
- sysendTestHelper.resetHistory()
- })
+ )
cy.wait('@compile')
@@ -270,6 +267,8 @@ describe.skip('', function () {
)
})
+ cy.spy(detachChannel, 'postMessage').as('postDetachMessage')
+
const syncing = interceptSyncCodeAsync()
cy.findByRole('button', {
@@ -290,18 +289,14 @@ describe.skip('', function () {
cy.findByRole('button', {
name: 'Go to code location in PDF',
- }).should(() => {
- const message = sysendTestHelper.getMessageWithEvent(
- 'action-setHighlights'
- )
+ }).should('not.be.disabled')
- // synctex is called locally and the result are broadcast for the detached tab
- // NOTE: can't use `.to.deep.include({…})` as it doesn't match the nested array
- expect(message).to.deep.equal({
- role: 'detacher',
- event: 'action-setHighlights',
- data: { args: [mockHighlights] },
- })
+ // synctex is called locally and the result are broadcast for the detached tab
+ // NOTE: can't use `.to.deep.include({…})` as it doesn't match the nested array
+ cy.get('@postDetachMessage').should('be.calledWith', {
+ role: 'detacher',
+ event: 'action-setHighlights',
+ data: { args: [mockHighlights] },
})
})
@@ -317,7 +312,7 @@ describe.skip('', function () {
).then(() => {
- sysendTestHelper.receiveMessage({
+ testDetachChannel.postMessage({
role: 'detached',
event: 'action-sync-to-code',
data: {
@@ -362,7 +357,7 @@ describe.skip('', function () {
)
cy.wait('@compile').then(() => {
- sysendTestHelper.receiveMessage({
+ testDetachChannel.postMessage({
role: 'detacher',
event: `state-position`,
data: { value: mockPosition },
@@ -373,36 +368,28 @@ describe.skip('', function () {
name: /^Go to PDF location in code/,
})
- cy.findByRole('button', { name: /^Go to PDF location in code/ })
- .should('not.be.disabled')
- .then(() => {
- sysendTestHelper.resetHistory()
- })
+ cy.findByRole('button', { name: /^Go to PDF location in code/ }).should(
+ 'not.be.disabled'
+ )
+
+ cy.spy(detachChannel, 'postMessage').as('postDetachMessage')
cy.findByRole('button', { name: /^Go to PDF location in code/ }).click()
- // the button is only disabled when the state is updated via sysend
+ // the button is only disabled when the state is updated
cy.findByRole('button', { name: /^Go to PDF location in code/ }).should(
'not.be.disabled'
)
cy.get('.synctex-spin-icon').should('not.exist')
- cy.findByRole('button', { name: /^Go to PDF location in code/ }).should(
- () => {
- const message = sysendTestHelper.getMessageWithEvent(
- 'action-sync-to-code'
- )
-
- expect(message).to.deep.equal({
- role: 'detached',
- event: 'action-sync-to-code',
- data: {
- args: [mockPosition, 72],
- },
- })
- }
- )
+ cy.get('@postDetachMessage').should('be.calledWith', {
+ role: 'detached',
+ event: 'action-sync-to-code',
+ data: {
+ args: [mockPosition, 72],
+ },
+ })
})
it('update inflight state', function () {
@@ -413,8 +400,10 @@ describe.skip('', function () {
- ).then(() => {
- sysendTestHelper.receiveMessage({
+ )
+
+ cy.wrap(null).then(() => {
+ testDetachChannel.postMessage({
role: 'detacher',
event: `state-position`,
data: { value: mockPosition },
@@ -425,29 +414,29 @@ describe.skip('', function () {
'not.be.disabled'
)
- cy.get('.synctex-spin-icon')
- .should('not.exist')
- .then(() => {
- sysendTestHelper.receiveMessage({
- role: 'detacher',
- event: 'state-sync-to-code-inflight',
- data: { value: true },
- })
+ cy.get('.synctex-spin-icon').should('not.exist')
+
+ cy.wrap(null).then(() => {
+ testDetachChannel.postMessage({
+ role: 'detacher',
+ event: 'state-sync-to-code-inflight',
+ data: { value: true },
})
+ })
cy.findByRole('button', { name: /^Go to PDF location in code/ }).should(
'be.disabled'
)
- cy.get('.synctex-spin-icon')
- .should('have.length', 1)
- .then(() => {
- sysendTestHelper.receiveMessage({
- role: 'detacher',
- event: 'state-sync-to-code-inflight',
- data: { value: false },
- })
+ cy.get('.synctex-spin-icon').should('have.length', 1)
+
+ cy.wrap(null).then(() => {
+ testDetachChannel.postMessage({
+ role: 'detacher',
+ event: 'state-sync-to-code-inflight',
+ data: { value: false },
})
+ })
cy.findByRole('button', { name: /^Go to PDF location in code/ }).should(
'not.be.disabled'
diff --git a/services/web/test/frontend/helpers/detach-channel.ts b/services/web/test/frontend/helpers/detach-channel.ts
new file mode 100644
index 0000000000..bd140bfa38
--- /dev/null
+++ b/services/web/test/frontend/helpers/detach-channel.ts
@@ -0,0 +1,10 @@
+import {
+ detachChannelId,
+ detachChannel as _detachChannel,
+} from '../../../frontend/js/shared/context/detach-context'
+
+// for tests, assert that detachChannel is defined, as BroadcastChannel is available
+export const detachChannel = _detachChannel!
+
+// simulate messages from another tab by posting them to this channel
+export const testDetachChannel = new BroadcastChannel(detachChannelId)
diff --git a/services/web/test/frontend/helpers/render-with-context.js b/services/web/test/frontend/helpers/render-with-context.js
index eee4ab782c..d6d1146225 100644
--- a/services/web/test/frontend/helpers/render-with-context.js
+++ b/services/web/test/frontend/helpers/render-with-context.js
@@ -2,7 +2,6 @@
/* eslint-disable react/prop-types */
import { render } from '@testing-library/react'
-import { renderHook } from '@testing-library/react-hooks'
import { ChatProvider } from '../../../frontend/js/features/chat/context/chat-context'
import { EditorProviders } from './editor-providers'
@@ -21,14 +20,6 @@ export function renderWithEditorContext(
})
}
-export function renderHookWithEditorContext(hook, contextProps) {
- const EditorProvidersWrapper = ({ children }) => (
- {children}
- )
-
- return renderHook(hook, { wrapper: EditorProvidersWrapper })
-}
-
export function ChatProviders({ children, ...props }) {
return (
diff --git a/services/web/test/frontend/helpers/sysend.js b/services/web/test/frontend/helpers/sysend.js
deleted file mode 100644
index cffa687f21..0000000000
--- a/services/web/test/frontend/helpers/sysend.js
+++ /dev/null
@@ -1,53 +0,0 @@
-import sysend from 'sysend'
-import sinon from 'sinon'
-
-const sysendSpy = sinon.spy(sysend)
-
-function resetHistory() {
- for (const method of Object.keys(sysendSpy)) {
- if (sysendSpy[method].resetHistory) sysendSpy[method].resetHistory()
- }
-}
-
-// sysends sends and receives custom calls in the background. This Helps
-// filtering them out
-function getDetachCalls(method) {
- return sysend[method]
- .getCalls()
- .filter(call => call.args[0].startsWith('detach-'))
-}
-
-function getLastDetachCall(method) {
- return getDetachCalls(method).pop()
-}
-
-function getLastBroacastMessage() {
- return getLastDetachCall('broadcast')?.args[1]
-}
-
-function getAllBroacastMessages() {
- return getDetachCalls('broadcast')
-}
-
-// this fakes receiving a message by calling the handler add to `on`. A bit
-// funky, but works for now
-function receiveMessage(message) {
- getLastDetachCall('on').args[1](message)
-}
-
-function getMessageWithEvent(eventName) {
- return getAllBroacastMessages()
- .map(item => item.args[1])
- .find(item => item.event === eventName)
-}
-
-export default {
- spy: sysendSpy,
- resetHistory,
- getDetachCalls,
- getLastDetachCall,
- getLastBroacastMessage,
- getAllBroacastMessages,
- getMessageWithEvent,
- receiveMessage,
-}
diff --git a/services/web/test/frontend/shared/hooks/use-detach-action.spec.tsx b/services/web/test/frontend/shared/hooks/use-detach-action.spec.tsx
new file mode 100644
index 0000000000..567e547e48
--- /dev/null
+++ b/services/web/test/frontend/shared/hooks/use-detach-action.spec.tsx
@@ -0,0 +1,121 @@
+import { FC } from 'react'
+import useDetachAction from '../../../../frontend/js/shared/hooks/use-detach-action'
+import { detachChannel, testDetachChannel } from '../../helpers/detach-channel'
+import { EditorProviders } from '../../helpers/editor-providers'
+
+const DetachActionTest: FC<{
+ actionName: string
+ actionFunction: () => void
+ handleClick: (trigger: (value: any) => void) => void
+}> = ({ actionName, actionFunction, handleClick }) => {
+ const trigger = useDetachAction(
+ actionName,
+ actionFunction,
+ 'detacher',
+ 'detached'
+ )
+
+ return (
+
+ )
+}
+
+describe('useDetachAction', function () {
+ beforeEach(function () {
+ window.metaAttributesCache = new Map()
+ })
+
+ afterEach(function () {
+ window.metaAttributesCache = new Map()
+ })
+
+ it('broadcast message as sender', function () {
+ window.metaAttributesCache.set('ol-detachRole', 'detacher')
+
+ cy.mount(
+
+ trigger('foo')}
+ />
+
+ )
+
+ cy.spy(detachChannel, 'postMessage').as('postDetachMessage')
+ cy.get('#trigger').click()
+ cy.get('@postDetachMessage').should('be.calledWith', {
+ role: 'detacher',
+ event: 'action-some-action',
+ data: { args: ['foo'] },
+ })
+ cy.get('@actionFunction').should('not.be.called')
+ })
+
+ it('call function as non-sender', function () {
+ cy.mount(
+
+ trigger('foo')}
+ />
+
+ )
+
+ cy.spy(detachChannel, 'postMessage').as('postDetachMessage')
+ cy.get('#trigger').click()
+ cy.get('@postDetachMessage').should('not.be.called')
+ cy.get('@actionFunction').should('be.calledWith', 'foo')
+ })
+
+ it('receive message and call function as target', function () {
+ window.metaAttributesCache.set('ol-detachRole', 'detached')
+
+ cy.mount(
+
+ trigger('foo')}
+ />
+
+ )
+
+ cy.wrap(null).then(() => {
+ testDetachChannel.postMessage({
+ role: 'detached',
+ event: 'action-some-action',
+ data: { args: ['foo'] },
+ })
+ })
+
+ cy.get('@actionFunction').should('be.calledWith', 'foo')
+ })
+
+ it('receive message and does not call function as non-target', function () {
+ window.metaAttributesCache.set('ol-detachRole', 'detacher')
+
+ cy.mount(
+
+ trigger('foo')}
+ />
+
+ )
+
+ cy.wrap(null).then(() => {
+ testDetachChannel.postMessage({
+ role: 'detached',
+ event: 'action-some-action',
+ data: { args: [] },
+ })
+ })
+
+ cy.get('@actionFunction').should('not.be.called')
+ })
+})
diff --git a/services/web/test/frontend/shared/hooks/use-detach-action.test.js b/services/web/test/frontend/shared/hooks/use-detach-action.test.js
deleted file mode 100644
index 9c0bdfd955..0000000000
--- a/services/web/test/frontend/shared/hooks/use-detach-action.test.js
+++ /dev/null
@@ -1,82 +0,0 @@
-import sinon from 'sinon'
-import { expect } from 'chai'
-import { renderHookWithEditorContext } from '../../helpers/render-with-context'
-import sysendTestHelper from '../../helpers/sysend'
-import useDetachAction from '../../../../frontend/js/shared/hooks/use-detach-action'
-
-const actionName = 'some-action'
-const actionFunction = sinon.stub()
-
-describe('useDetachAction', function () {
- beforeEach(function () {
- window.metaAttributesCache = new Map()
- })
-
- afterEach(function () {
- window.metaAttributesCache = new Map()
- actionFunction.reset()
- })
-
- it('broadcast message as sender', async function () {
- window.metaAttributesCache.set('ol-detachRole', 'detacher')
- const { result } = renderHookWithEditorContext(() =>
- useDetachAction(actionName, actionFunction, 'detacher', 'detached')
- )
- const triggerFn = result.current
- sysendTestHelper.resetHistory()
-
- triggerFn('param')
-
- expect(sysendTestHelper.getLastBroacastMessage()).to.deep.equal({
- role: 'detacher',
- event: `action-${actionName}`,
- data: { args: ['param'] },
- })
-
- sinon.assert.notCalled(actionFunction)
- })
-
- it('call function as non-sender', async function () {
- const { result } = renderHookWithEditorContext(() =>
- useDetachAction(actionName, actionFunction, 'detacher', 'detached')
- )
- const triggerFn = result.current
- sysendTestHelper.resetHistory()
-
- triggerFn('param')
-
- expect(sysendTestHelper.getDetachCalls('broadcast').length).to.equal(0)
-
- sinon.assert.calledWith(actionFunction, 'param')
- })
-
- it('receive message and call function as target', async function () {
- window.metaAttributesCache.set('ol-detachRole', 'detached')
- renderHookWithEditorContext(() =>
- useDetachAction(actionName, actionFunction, 'detacher', 'detached')
- )
-
- sysendTestHelper.receiveMessage({
- role: 'detached',
- event: `action-${actionName}`,
- data: { args: ['param'] },
- })
-
- sinon.assert.calledWith(actionFunction, 'param')
- })
-
- it('receive message and does not call function as non-target', async function () {
- window.metaAttributesCache.set('ol-detachRole', 'detacher')
- renderHookWithEditorContext(() =>
- useDetachAction(actionName, actionFunction, 'detacher', 'detached')
- )
-
- sysendTestHelper.receiveMessage({
- role: 'detached',
- event: `action-${actionName}`,
- data: { args: [] },
- })
-
- sinon.assert.notCalled(actionFunction)
- })
-})
diff --git a/services/web/test/frontend/shared/hooks/use-detach-layout.spec.tsx b/services/web/test/frontend/shared/hooks/use-detach-layout.spec.tsx
new file mode 100644
index 0000000000..931da32ac7
--- /dev/null
+++ b/services/web/test/frontend/shared/hooks/use-detach-layout.spec.tsx
@@ -0,0 +1,244 @@
+import useDetachLayout from '../../../../frontend/js/shared/hooks/use-detach-layout'
+import { detachChannel, testDetachChannel } from '../../helpers/detach-channel'
+import { EditorProviders } from '../../helpers/editor-providers'
+import { Button, Checkbox, ControlLabel, FormGroup } from 'react-bootstrap'
+
+const DetachLayoutTest = () => {
+ const { role, reattach, detach, isLinked, isLinking, isRedundant } =
+ useDetachLayout()
+
+ return (
+
+ )
+}
+
+describe('useDetachLayout', function () {
+ beforeEach(function () {
+ window.metaAttributesCache = new Map()
+ cy.stub(window, 'open').as('openWindow')
+ cy.stub(window, 'close').as('closeWindow')
+ })
+
+ afterEach(function () {
+ window.metaAttributesCache = new Map()
+ })
+
+ it('detaching', function () {
+ // 1. create hook in normal mode
+ cy.mount(
+
+
+
+ )
+
+ cy.get('#isLinked').should('not.be.checked')
+ cy.get('#isLinking').should('not.be.checked')
+ cy.get('#role').should('have.text', 'none')
+
+ // 2. detach
+ cy.get('#detach').click()
+ cy.get('@openWindow').should(
+ 'be.calledOnceWith',
+ Cypress.sinon.match(/\/detached$/),
+ '_blank'
+ )
+ cy.get('#isLinked').should('not.be.checked')
+ cy.get('#isLinking').should('be.checked')
+ cy.get('#role').should('have.text', 'detacher')
+ })
+
+ it('detacher role', function () {
+ // 1. create hook in detacher mode
+ window.metaAttributesCache.set('ol-detachRole', 'detacher')
+
+ cy.mount(
+
+
+
+ )
+
+ cy.get('#isLinked').should('not.be.checked')
+ cy.get('#isLinking').should('not.be.checked')
+ cy.get('#role').should('have.text', 'detacher')
+
+ cy.spy(detachChannel, 'postMessage').as('postDetachMessage')
+
+ cy.wrap(null).then(() => {
+ testDetachChannel.postMessage({
+ role: 'detached',
+ event: 'connected',
+ })
+ })
+
+ // 2. simulate connected detached tab
+ cy.get('@postDetachMessage').should('be.calledWith', {
+ role: 'detacher',
+ event: 'up',
+ })
+
+ cy.get('#isLinked').should('be.checked')
+ cy.get('#isLinking').should('not.be.checked')
+ cy.get('#role').should('have.text', 'detacher')
+
+ // 3. simulate closed detached tab
+ cy.wrap(null).then(() => {
+ testDetachChannel.postMessage({
+ role: 'detached',
+ event: 'closed',
+ })
+ })
+ cy.get('#isLinked').should('not.be.checked')
+ cy.get('#isLinking').should('not.be.checked')
+ cy.get('#role').should('have.text', 'detacher')
+
+ // 4. simulate up detached tab
+ cy.wrap(null).then(() => {
+ testDetachChannel.postMessage({
+ role: 'detached',
+ event: 'up',
+ })
+ })
+
+ cy.get('#isLinked').should('be.checked')
+ cy.get('#isLinking').should('not.be.checked')
+ cy.get('#role').should('have.text', 'detacher')
+
+ // 5. reattach
+ cy.get('@postDetachMessage').invoke('resetHistory')
+ cy.get('#reattach').click()
+
+ cy.get('#isLinked').should('not.be.checked')
+ cy.get('#isLinking').should('not.be.checked')
+ cy.get('#role').should('have.text', 'none')
+ cy.get('@postDetachMessage').should('be.calledWith', {
+ role: 'detacher',
+ event: 'reattach',
+ })
+ })
+
+ it('reset detacher role when other detacher tab connects', function () {
+ // 1. create hook in detacher mode
+ window.metaAttributesCache.set('ol-detachRole', 'detacher')
+
+ cy.mount(
+
+
+
+ )
+
+ cy.get('#isLinked').should('not.be.checked')
+ cy.get('#isLinking').should('not.be.checked')
+ cy.get('#role').should('have.text', 'detacher')
+
+ // 2. simulate other detacher tab
+ cy.wrap(null).then(() => {
+ testDetachChannel.postMessage({
+ role: 'detacher',
+ event: 'up',
+ })
+ })
+
+ cy.get('#isRedundant').should('be.checked')
+ cy.get('#role').should('have.text', 'none')
+ })
+
+ it('detached role', function () {
+ // 1. create hook in detached mode
+ window.metaAttributesCache.set('ol-detachRole', 'detached')
+
+ cy.mount(
+
+
+
+ )
+
+ cy.get('#isLinked').should('not.be.checked')
+ cy.get('#isLinking').should('not.be.checked')
+ cy.get('#role').should('have.text', 'detached')
+
+ // 2. simulate up detacher tab
+ cy.wrap(null).then(() => {
+ testDetachChannel.postMessage({
+ role: 'detacher',
+ event: 'up',
+ })
+ })
+
+ cy.get('#isLinked').should('be.checked')
+ cy.get('#isLinking').should('not.be.checked')
+ cy.get('#role').should('have.text', 'detached')
+
+ // 3. simulate closed detacher tab
+ cy.wrap(null).then(() => {
+ testDetachChannel.postMessage({
+ role: 'detacher',
+ event: 'closed',
+ })
+ })
+ cy.get('#isLinked').should('not.be.checked')
+ cy.get('#isLinking').should('not.be.checked')
+ cy.get('#role').should('have.text', 'detached')
+
+ // 4. simulate up detacher tab
+ cy.wrap(null).then(() => {
+ testDetachChannel.postMessage({
+ role: 'detacher',
+ event: 'up',
+ })
+ })
+ cy.get('#isLinked').should('be.checked')
+ cy.get('#isLinking').should('not.be.checked')
+ cy.get('#role').should('have.text', 'detached')
+
+ // 5. simulate closed detached tab
+ cy.spy(detachChannel, 'postMessage').as('postDetachMessage')
+ cy.wrap(null).then(() => {
+ testDetachChannel.postMessage({
+ role: 'detached',
+ event: 'closed',
+ })
+ })
+ cy.get('#isLinked').should('be.checked')
+ cy.get('#isLinking').should('not.be.checked')
+ cy.get('#role').should('have.text', 'detached')
+ cy.get('@postDetachMessage').should('be.calledWith', {
+ role: 'detached',
+ event: 'up',
+ })
+
+ // 6. simulate reattach event
+ cy.get('@postDetachMessage').invoke('resetHistory')
+ cy.wrap(null).then(() => {
+ testDetachChannel.postMessage({
+ role: 'detacher',
+ event: 'reattach',
+ })
+ })
+ cy.get('#isLinked').should('not.be.checked')
+ cy.get('#isLinking').should('not.be.checked')
+ cy.get('#role').should('have.text', 'detached')
+ cy.get('@closeWindow').should('be.called')
+ })
+})
diff --git a/services/web/test/frontend/shared/hooks/use-detach-layout.test.js b/services/web/test/frontend/shared/hooks/use-detach-layout.test.js
deleted file mode 100644
index 359df11cf4..0000000000
--- a/services/web/test/frontend/shared/hooks/use-detach-layout.test.js
+++ /dev/null
@@ -1,210 +0,0 @@
-import { waitFor } from '@testing-library/react'
-import { act } from '@testing-library/react-hooks'
-import { expect } from 'chai'
-import sinon from 'sinon'
-import { renderHookWithEditorContext } from '../../helpers/render-with-context'
-import sysendTestHelper from '../../helpers/sysend'
-import useDetachLayout from '../../../../frontend/js/shared/hooks/use-detach-layout'
-
-describe('useDetachLayout', function () {
- let openStub
- let closeStub
-
- beforeEach(function () {
- window.metaAttributesCache = new Map()
- openStub = sinon.stub(window, 'open')
- closeStub = sinon.stub(window, 'close')
- })
-
- afterEach(function () {
- window.metaAttributesCache = new Map()
- openStub.restore()
- closeStub.restore()
- })
-
- it('detaching', async function () {
- // 1. create hook in normal mode
- const { result } = renderHookWithEditorContext(() => useDetachLayout())
- expect(result.current.reattach).to.be.a('function')
- expect(result.current.detach).to.be.a('function')
- expect(result.current.isLinked).to.be.false
- expect(result.current.isLinking).to.be.false
- expect(result.current.role).to.be.null
-
- // 2. detach
- act(() => {
- result.current.detach()
- })
- expect(result.current.isLinked).to.be.false
- expect(result.current.isLinking).to.be.true
- expect(result.current.role).to.equal('detacher')
- sinon.assert.calledOnce(openStub)
- sinon.assert.calledWith(
- openStub,
- 'https://www.test-overleaf.com/detached',
- '_blank'
- )
- })
-
- it('detacher role', async function () {
- sysendTestHelper.spy.broadcast.resetHistory()
- window.metaAttributesCache.set('ol-detachRole', 'detacher')
-
- // 1. create hook in detacher mode
- const { result } = renderHookWithEditorContext(() => useDetachLayout())
- expect(result.current.reattach).to.be.a('function')
- expect(result.current.detach).to.be.a('function')
- expect(result.current.isLinked).to.be.false
- expect(result.current.isLinking).to.be.false
- expect(result.current.role).to.equal('detacher')
- const broadcastMessagesCount =
- sysendTestHelper.getAllBroacastMessages().length
-
- // 2. simulate connected detached tab
- sysendTestHelper.spy.broadcast.resetHistory()
- sysendTestHelper.receiveMessage({
- role: 'detached',
- event: 'connected',
- })
- expect(sysendTestHelper.getLastBroacastMessage()).to.deep.equal({
- role: 'detacher',
- event: 'up',
- })
- expect(result.current.isLinked).to.be.true
- expect(result.current.isLinking).to.be.false
- expect(result.current.role).to.equal('detacher')
-
- // check that all message were re-broadcast for the new tab
- await nextTick() // necessary to ensure all event handler have run
- await waitFor(() => {
- const reBroadcastMessagesCount =
- sysendTestHelper.getAllBroacastMessages().length
- expect(reBroadcastMessagesCount).to.equal(broadcastMessagesCount)
- })
- // 3. simulate closed detached tab
- sysendTestHelper.spy.broadcast.resetHistory()
- sysendTestHelper.receiveMessage({
- role: 'detached',
- event: 'closed',
- })
- expect(result.current.isLinked).to.be.false
- expect(result.current.isLinking).to.be.false
- expect(result.current.role).to.equal('detacher')
-
- // 4. simulate up detached tab
- sysendTestHelper.spy.broadcast.resetHistory()
- sysendTestHelper.receiveMessage({
- role: 'detached',
- event: 'up',
- })
- expect(result.current.isLinked).to.be.true
- expect(result.current.isLinking).to.be.false
- expect(result.current.role).to.equal('detacher')
-
- // 5. reattach
- sysendTestHelper.spy.broadcast.resetHistory()
- act(() => {
- result.current.reattach()
- })
- expect(result.current.isLinked).to.be.false
- expect(result.current.isLinking).to.be.false
- expect(result.current.role).to.be.null
- expect(sysendTestHelper.getLastBroacastMessage()).to.deep.equal({
- role: 'detacher',
- event: 'reattach',
- })
- })
-
- it('reset detacher role when other detacher tab connects', function () {
- window.metaAttributesCache.set('ol-detachRole', 'detacher')
-
- // 1. create hook in detacher mode
- const { result } = renderHookWithEditorContext(() => useDetachLayout())
- expect(result.current.reattach).to.be.a('function')
- expect(result.current.detach).to.be.a('function')
- expect(result.current.isLinked).to.be.false
- expect(result.current.isLinking).to.be.false
- expect(result.current.role).to.equal('detacher')
-
- // 2. simulate other detacher tab
- sysendTestHelper.receiveMessage({
- role: 'detacher',
- event: 'up',
- })
- expect(result.current.isRedundant).to.be.true
- expect(result.current.role).to.equal(null)
- })
-
- it('detached role', async function () {
- window.metaAttributesCache.set('ol-detachRole', 'detached')
-
- // 1. create hook in detached mode
- const { result } = renderHookWithEditorContext(() => useDetachLayout())
- expect(result.current.reattach).to.be.a('function')
- expect(result.current.detach).to.be.a('function')
- expect(result.current.isLinked).to.be.false
- expect(result.current.isLinking).to.be.false
- expect(result.current.role).to.equal('detached')
-
- // 2. simulate up detacher tab
- sysendTestHelper.spy.broadcast.resetHistory()
- sysendTestHelper.receiveMessage({
- role: 'detacher',
- event: 'up',
- })
- expect(result.current.isLinked).to.be.true
- expect(result.current.isLinking).to.be.false
- expect(result.current.role).to.equal('detached')
-
- // 3. simulate closed detacher tab
- sysendTestHelper.spy.broadcast.resetHistory()
- sysendTestHelper.receiveMessage({
- role: 'detacher',
- event: 'closed',
- })
- expect(result.current.isLinked).to.be.false
- expect(result.current.isLinking).to.be.false
- expect(result.current.role).to.equal('detached')
-
- // 4. simulate up detacher tab
- sysendTestHelper.spy.broadcast.resetHistory()
- sysendTestHelper.receiveMessage({
- role: 'detacher',
- event: 'up',
- })
- expect(result.current.isLinked).to.be.true
- expect(result.current.isLinking).to.be.false
- expect(result.current.role).to.equal('detached')
-
- // 5. simulate closed detached tab
- sysendTestHelper.spy.broadcast.resetHistory()
- sysendTestHelper.receiveMessage({
- role: 'detached',
- event: 'closed',
- })
- expect(result.current.isLinked).to.be.true
- expect(result.current.isLinking).to.be.false
- expect(result.current.role).to.equal('detached')
- expect(sysendTestHelper.getLastBroacastMessage()).to.deep.equal({
- role: 'detached',
- event: 'up',
- })
-
- // 6. simulate reattach event
- sysendTestHelper.spy.broadcast.resetHistory()
- sysendTestHelper.receiveMessage({
- role: 'detacher',
- event: 'reattach',
- })
- expect(result.current.isLinked).to.be.false
- expect(result.current.isLinking).to.be.false
- expect(result.current.role).to.equal('detached')
- sinon.assert.called(closeStub)
- })
-})
-
-const nextTick = () => {
- return new Promise(resolve => {
- setTimeout(resolve)
- })
-}
diff --git a/services/web/test/frontend/shared/hooks/use-detach-state.spec.tsx b/services/web/test/frontend/shared/hooks/use-detach-state.spec.tsx
new file mode 100644
index 0000000000..429a38c431
--- /dev/null
+++ b/services/web/test/frontend/shared/hooks/use-detach-state.spec.tsx
@@ -0,0 +1,108 @@
+import { FC } from 'react'
+import useDetachState from '../../../../frontend/js/shared/hooks/use-detach-state'
+import { EditorProviders } from '../../helpers/editor-providers'
+import { detachChannel, testDetachChannel } from '../../helpers/detach-channel'
+
+const DetachStateTest: FC<{
+ stateKey: string
+ defaultValue: any
+ senderRole?: string
+ targetRole?: string
+ handleClick: (setValue: (value: any) => void) => void
+}> = ({ stateKey, defaultValue, handleClick, senderRole, targetRole }) => {
+ const [value, setValue] = useDetachState(
+ stateKey,
+ defaultValue,
+ senderRole,
+ targetRole
+ )
+
+ return (
+
+
{value}
+
+
+ )
+}
+
+describe('useDetachState', function () {
+ beforeEach(function () {
+ window.metaAttributesCache = new Map()
+ })
+
+ afterEach(function () {
+ window.metaAttributesCache = new Map()
+ })
+
+ it('create and update state', function () {
+ cy.mount(
+
+ {
+ setValue('barbaz')
+ }}
+ />
+
+ )
+
+ cy.get('#value').should('have.text', 'foobar')
+ cy.get('#setValue').click()
+ cy.get('#value').should('have.text', 'barbaz')
+ })
+
+ it('broadcast message as sender', function () {
+ window.metaAttributesCache.set('ol-detachRole', 'detacher')
+
+ cy.mount(
+
+ {
+ setValue('barbaz1')
+ }}
+ />
+
+ )
+
+ cy.spy(detachChannel, 'postMessage').as('postDetachMessage')
+ cy.get('#setValue').click()
+ cy.get('@postDetachMessage').should('be.calledWith', {
+ role: 'detacher',
+ event: 'state-some-key',
+ data: { value: 'barbaz1' },
+ })
+ })
+
+ it('receive message as target', function () {
+ window.metaAttributesCache.set('ol-detachRole', 'detached')
+
+ cy.mount(
+
+ {}}
+ />
+
+ )
+
+ cy.wrap(null).then(() => {
+ testDetachChannel.postMessage({
+ role: 'detached',
+ event: 'state-some-key',
+ data: { value: 'barbaz2' },
+ })
+ })
+
+ cy.get('#value').should('have.text', 'barbaz2')
+ })
+})
diff --git a/services/web/test/frontend/shared/hooks/use-detach-state.test.js b/services/web/test/frontend/shared/hooks/use-detach-state.test.js
deleted file mode 100644
index 520bdd6b22..0000000000
--- a/services/web/test/frontend/shared/hooks/use-detach-state.test.js
+++ /dev/null
@@ -1,68 +0,0 @@
-import { act } from '@testing-library/react-hooks'
-import { expect } from 'chai'
-import { renderHookWithEditorContext } from '../../helpers/render-with-context'
-import sysendTestHelper from '../../helpers/sysend'
-import useDetachState from '../../../../frontend/js/shared/hooks/use-detach-state'
-
-const stateKey = 'some-key'
-
-describe('useDetachState', function () {
- beforeEach(function () {
- window.metaAttributesCache = new Map()
- })
-
- afterEach(function () {
- window.metaAttributesCache = new Map()
- })
-
- it('create and update state', async function () {
- const defaultValue = 'foobar'
- const { result } = renderHookWithEditorContext(() =>
- useDetachState(stateKey, defaultValue)
- )
- const [value, setValue] = result.current
- expect(value).to.equal(defaultValue)
- expect(setValue).to.be.a('function')
-
- const newValue = 'barbaz'
- act(() => {
- setValue(newValue)
- })
- expect(result.current[0]).to.equal(newValue)
- })
-
- it('broadcast message as sender', async function () {
- window.metaAttributesCache.set('ol-detachRole', 'detacher')
- const { result } = renderHookWithEditorContext(() =>
- useDetachState(stateKey, null, 'detacher', 'detached')
- )
- const [, setValue] = result.current
- sysendTestHelper.resetHistory()
-
- const newValue = 'barbaz'
- act(() => {
- setValue(newValue)
- })
- expect(sysendTestHelper.getLastBroacastMessage()).to.deep.equal({
- role: 'detacher',
- event: `state-${stateKey}`,
- data: { value: newValue },
- })
- })
-
- it('receive message as target', async function () {
- window.metaAttributesCache.set('ol-detachRole', 'detached')
- const { result } = renderHookWithEditorContext(() =>
- useDetachState(stateKey, null, 'detacher', 'detached')
- )
-
- const newValue = 'barbaz'
- sysendTestHelper.receiveMessage({
- role: 'detached',
- event: `state-${stateKey}`,
- data: { value: newValue },
- })
-
- expect(result.current[0]).to.equal(newValue)
- })
-})