Merge pull request #6053 from overleaf/ta-pdf-detach-tests

PDF Detach Misc Tests

GitOrigin-RevId: 9615c8fdfd8964a9c63d7c91e4596d397a1d35dc
This commit is contained in:
Timothée Alby 2021-12-14 14:24:53 +01:00 committed by Copybot
parent 1201a733d9
commit 44eca312ff
16 changed files with 817 additions and 28 deletions

View file

@ -4,7 +4,7 @@ import { memo } from 'react'
import { buildUrlWithDetachRole } from '../../../shared/utils/url-helper'
const redirect = function () {
window.location = buildUrlWithDetachRole(null)
window.location = buildUrlWithDetachRole(null).toString()
}
function PdfOrphanRefreshButton() {

View file

@ -40,7 +40,7 @@ export function DetachProvider({ children }) {
if (debugPdfDetach) {
console.log('Effect', { role })
}
window.history.replaceState({}, '', buildUrlWithDetachRole(role))
window.history.replaceState({}, '', buildUrlWithDetachRole(role).toString())
}, [role])
useEffect(() => {
@ -72,11 +72,14 @@ export function DetachProvider({ children }) {
data,
})
}
sysend.broadcast(SYSEND_CHANNEL, {
const message = {
role,
event,
data,
})
}
if (data) {
message.data = data
}
sysend.broadcast(SYSEND_CHANNEL, message)
},
[role]
)

View file

@ -75,7 +75,7 @@ export default function useDetachLayout() {
setRole('detacher')
setIsLinking(true)
window.open(buildUrlWithDetachRole('detached'), '_blank')
window.open(buildUrlWithDetachRole('detached').toString(), '_blank')
}, [setRole, setIsLinking])
const handleEventForDetacherFromDetached = useCallback(

View file

@ -1,11 +1,11 @@
export function buildUrlWithDetachRole(mode) {
const url = new URL(window.location)
const cleanPathname = url.pathname
let cleanPathname = url.pathname
.replace(/\/(detached|detacher)\/?$/, '')
.replace(/\/$/, '')
url.pathname = cleanPathname
if (mode) {
url.pathname += `/${mode}`
cleanPathname += `/${mode}`
}
url.pathname = cleanPathname
return url
}

View file

@ -1,10 +1,12 @@
import _ from 'lodash'
// cache for parsed values
const cache = new Map()
window.metaAttributesCache = window.metaAttributesCache || new Map()
export default function getMeta(name, fallback) {
if (cache.has(name)) return cache.get(name)
if (window.metaAttributesCache.has(name)) {
return window.metaAttributesCache.get(name)
}
const element = document.head.querySelector(`meta[name="${name}"]`)
if (!element) {
return fallback
@ -28,7 +30,7 @@ export default function getMeta(name, fallback) {
default:
value = plainTextValue
}
cache.set(name, value)
window.metaAttributesCache.set(name, value)
return value
}

View file

@ -1,7 +1,12 @@
import sinon from 'sinon'
import fetchMock from 'fetch-mock'
import { expect } from 'chai'
import { fireEvent, screen } 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 '../../../../../frontend/js/infrastructure/event-tracking'
const eventTrackingSpy = sinon.spy(eventTracking)
describe('<LayoutDropdownButton />', function () {
let openStub
@ -12,10 +17,14 @@ describe('<LayoutDropdownButton />', function () {
beforeEach(function () {
openStub = sinon.stub(window, 'open')
window.metaAttributesCache = new Map()
fetchMock.post('express:/project/:projectId/compile/stop', () => 204)
})
afterEach(function () {
openStub.restore()
window.metaAttributesCache = new Map()
fetchMock.restore()
})
it('should mark current layout option as selected', function () {
@ -35,16 +44,66 @@ describe('<LayoutDropdownButton />', function () {
})
})
it('should show processing when detaching', function () {
renderWithEditorContext(<LayoutDropdownButton />, {
ui: { ...defaultUi, view: 'editor' },
describe('on detach', function () {
beforeEach(function () {
renderWithEditorContext(<LayoutDropdownButton />, {
ui: { ...defaultUi, view: 'editor' },
})
const menuItem = screen.getByRole('menuitem', {
name: 'PDF in separate tab',
})
fireEvent.click(menuItem)
})
const menuItem = screen.getByRole('menuitem', {
name: 'PDF in separate tab',
it('should show processing', function () {
screen.getByText('Layout processing')
})
fireEvent.click(menuItem)
screen.getByText('Layout processing')
it('should stop compile when detaching', function () {
expect(fetchMock.called('express:/project/:projectId/compile/stop')).to.be
.true
})
it('should record event', function () {
sinon.assert.calledWith(eventTrackingSpy.sendMB, 'project-layout-detach')
})
})
describe('on layout change / reattach', function () {
beforeEach(function () {
window.metaAttributesCache.set('ol-detachRole', 'detacher')
renderWithEditorContext(<LayoutDropdownButton />, {
ui: { ...defaultUi, view: 'editor' },
})
const menuItem = screen.getByRole('menuitem', {
name: 'Editor only (hide PDF)',
})
fireEvent.click(menuItem)
})
it('should not show processing', function () {
const processingText = screen.queryByText('Layout processing')
expect(processingText).to.not.exist
})
it('should record events', function () {
sinon.assert.calledWith(
eventTrackingSpy.sendMB,
'project-layout-reattach'
)
sinon.assert.calledWith(
eventTrackingSpy.sendMB,
'project-layout-change',
{ layout: 'flat', view: 'editor' }
)
})
it('should select new menu item', function () {
screen.getByRole('menuitem', {
name: 'Selected Editor only (hide PDF)',
})
})
})
})

View file

@ -0,0 +1,70 @@
import DetachCompileButton from '../../../../../frontend/js/features/pdf-preview/components/detach-compile-button'
import { renderWithEditorContext } from '../../../helpers/render-with-context'
import { screen, fireEvent } from '@testing-library/react'
import sysendTestHelper from '../../../helpers/sysend'
import { expect } from 'chai'
describe('<DetachCompileButton/>', function () {
afterEach(function () {
window.metaAttributesCache = new Map()
sysendTestHelper.resetHistory()
})
it('detacher mode and linked: show button ', async function () {
window.metaAttributesCache.set('ol-detachRole', 'detacher')
renderWithEditorContext(<DetachCompileButton />)
sysendTestHelper.receiveMessage({
role: 'detached',
event: 'connected',
})
await screen.getByRole('button', {
name: 'Recompile',
})
})
it('detacher mode and not linked: does not show button ', async function () {
window.metaAttributesCache.set('ol-detachRole', 'detacher')
renderWithEditorContext(<DetachCompileButton />)
expect(
await screen.queryByRole('button', {
name: 'Recompile',
})
).to.not.exist
})
it('not detacher mode and linked: does not show button ', async function () {
window.metaAttributesCache.set('ol-detachRole', 'detached')
renderWithEditorContext(<DetachCompileButton />)
sysendTestHelper.receiveMessage({
role: 'detacher',
event: 'connected',
})
expect(
await screen.queryByRole('button', {
name: 'Recompile',
})
).to.not.exist
})
it('send compile clicks via detached action', async function () {
window.metaAttributesCache.set('ol-detachRole', 'detacher')
renderWithEditorContext(<DetachCompileButton />)
sysendTestHelper.receiveMessage({
role: 'detached',
event: 'connected',
})
const compileButton = await screen.getByRole('button', {
name: 'Recompile',
})
fireEvent.click(compileButton)
expect(sysendTestHelper.getLastBroacastMessage()).to.deep.equal({
role: 'detacher',
event: 'action-start-compile',
data: { args: [] },
})
})
})

View file

@ -0,0 +1,26 @@
import PdfPreviewHybridToolbar from '../../../../../frontend/js/features/pdf-preview/components/pdf-preview-hybrid-toolbar'
import { renderWithEditorContext } from '../../../helpers/render-with-context'
import { screen } from '@testing-library/react'
describe('<PdfPreviewHybridToolbar/>', function () {
afterEach(function () {
window.metaAttributesCache = new Map()
})
it('shows normal mode', async function () {
renderWithEditorContext(<PdfPreviewHybridToolbar />)
await screen.getByRole('button', {
name: 'Recompile',
})
})
it('shows orphan mode', async function () {
window.metaAttributesCache.set('ol-detachRole', 'detached')
renderWithEditorContext(<PdfPreviewHybridToolbar />)
await screen.getByRole('button', {
name: 'Redirect to editor',
})
})
})

View file

@ -1,8 +1,9 @@
import PdfSynctexControls from '../../../../../frontend/js/features/pdf-preview/components/pdf-synctex-controls'
import { EditorProviders } from '../../../helpers/render-with-context'
import { renderWithEditorContext } from '../../../helpers/render-with-context'
import sysendTestHelper from '../../../helpers/sysend'
import { cloneDeep } from 'lodash'
import fetchMock from 'fetch-mock'
import { fireEvent, screen, waitFor, render } from '@testing-library/react'
import { fireEvent, screen, waitFor } from '@testing-library/react'
import fs from 'fs'
import path from 'path'
import { expect } from 'chai'
@ -95,19 +96,20 @@ const mockSynctex = () =>
describe('<PdfSynctexControls/>', function () {
beforeEach(function () {
window.showNewPdfPreview = true
window.metaAttributesCache = new Map()
fetchMock.restore()
mockCompile()
mockSynctex()
mockBuildFile()
})
afterEach(function () {
window.showNewPdfPreview = undefined
window.metaAttributesCache = new Map()
fetchMock.restore()
})
it('handles clicks on sync buttons', async function () {
mockCompile()
mockSynctex()
mockBuildFile()
const Inner = () => {
const { setPosition } = useCompileContext()
@ -123,11 +125,12 @@ describe('<PdfSynctexControls/>', function () {
return null
}
render(
<EditorProviders scope={scope}>
renderWithEditorContext(
<>
<Inner />
<PdfSynctexControls />
</EditorProviders>
</>,
{ scope }
)
const syncToPdfButton = await screen.findByRole('button', {
@ -164,4 +167,156 @@ describe('<PdfSynctexControls/>', function () {
.true
})
})
describe('with detacher role', async function () {
beforeEach(function () {
window.metaAttributesCache.set('ol-detachRole', 'detacher')
})
it('does not have go to PDF location button', async function () {
renderWithEditorContext(<PdfSynctexControls />, { scope })
expect(
await screen.queryByRole('button', {
name: 'Go to PDF location in code',
})
).to.not.exist
})
it('send go to PDF location action', async function () {
renderWithEditorContext(<PdfSynctexControls />, { scope })
sysendTestHelper.resetHistory()
const syncToPdfButton = await screen.findByRole('button', {
name: 'Go to code location in PDF',
})
// mock editor cursor position update
fireEvent(
window,
new CustomEvent('cursor:editor:update', {
detail: { row: 100, column: 10 },
})
)
fireEvent.click(syncToPdfButton)
// the button is only disabled when the state is updated via sysend
expect(syncToPdfButton.disabled).to.be.false
expect(sysendTestHelper.getLastBroacastMessage()).to.deep.equal({
role: 'detacher',
event: 'action-go-to-pdf-location',
data: { args: ['file=&line=101&column=10'] },
})
})
it('update inflight state', async function () {
renderWithEditorContext(<PdfSynctexControls />, { scope })
sysendTestHelper.resetHistory()
const syncToPdfButton = await screen.findByRole('button', {
name: 'Go to code location in PDF',
})
sysendTestHelper.receiveMessage({
role: 'detached',
event: 'state-sync-to-pdf-inflight',
data: { value: true },
})
expect(syncToPdfButton.disabled).to.be.true
sysendTestHelper.receiveMessage({
role: 'detached',
event: 'state-sync-to-pdf-inflight',
data: { value: false },
})
expect(syncToPdfButton.disabled).to.be.false
})
})
describe('with detached role', async function () {
beforeEach(function () {
window.metaAttributesCache.set('ol-detachRole', 'detached')
})
it('does not have go to code location button', async function () {
renderWithEditorContext(<PdfSynctexControls />, { scope })
expect(
await screen.queryByRole('button', {
name: 'Go to code location in PDF',
})
).to.not.exist
})
it('send go to code line action and update inflight state', async function () {
renderWithEditorContext(<PdfSynctexControls />, { scope })
sysendTestHelper.resetHistory()
const syncToCodeButton = await screen.findByRole('button', {
name: 'Go to PDF location in code',
})
sysendTestHelper.resetHistory()
fireEvent.click(syncToCodeButton)
expect(syncToCodeButton.disabled).to.be.true
await waitFor(() => {
expect(fetchMock.called('express:/project/:projectId/sync/pdf')).to.be
.true
})
expect(sysendTestHelper.getLastBroacastMessage()).to.deep.equal({
role: 'detached',
event: 'action-go-to-code-line',
data: { args: ['main.tex', 100] },
})
})
it('sends PDF exists state', async function () {
renderWithEditorContext(<PdfSynctexControls />, { scope })
sysendTestHelper.resetHistory()
await waitFor(() => {
expect(fetchMock.called('express:/project/:projectId/compile')).to.be
.true
})
expect(sysendTestHelper.getLastBroacastMessage()).to.deep.equal({
role: 'detached',
event: 'state-pdf-exists',
data: { value: true },
})
})
it('reacts to go to PDF location action', async function () {
renderWithEditorContext(<PdfSynctexControls />, { scope })
sysendTestHelper.resetHistory()
await waitFor(() => {
expect(fetchMock.called('express:/project/:projectId/compile')).to.be
.true
})
sysendTestHelper.spy.broadcast.resetHistory()
sysendTestHelper.receiveMessage({
role: 'detacher',
event: 'action-go-to-pdf-location',
data: { args: ['file=&line=101&column=10'] },
})
await waitFor(() => {
expect(fetchMock.called('express:/project/:projectId/sync/code')).to.be
.true
})
expect(sysendTestHelper.getLastBroacastMessage()).to.deep.equal({
role: 'detached',
event: 'state-sync-to-pdf-inflight',
data: { value: false },
})
})
})
})

View file

@ -2,6 +2,7 @@
/* eslint-disable react/prop-types */
import { render } from '@testing-library/react'
import { renderHook } from '@testing-library/react-hooks'
import sinon from 'sinon'
import { UserProvider } from '../../../frontend/js/shared/context/user-context'
import { EditorProvider } from '../../../frontend/js/shared/context/editor-context'
@ -122,6 +123,14 @@ export function renderWithEditorContext(component, contextProps) {
return render(component, { wrapper: EditorProvidersWrapper })
}
export function renderHookWithEditorContext(hook, contextProps) {
const EditorProvidersWrapper = ({ children }) => (
<EditorProviders {...contextProps}>{children}</EditorProviders>
)
return renderHook(hook, { wrapper: EditorProvidersWrapper })
}
export function ChatProviders({ children, ...props }) {
return (
<EditorProviders {...props}>

View file

@ -0,0 +1,41 @@
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]
}
// 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)
}
export default {
spy: sysendSpy,
resetHistory,
getDetachCalls,
getLastDetachCall,
getLastBroacastMessage,
receiveMessage,
}

View file

@ -0,0 +1,34 @@
import sinon from 'sinon'
import { renderHook } from '@testing-library/react-hooks'
import useCallbackHandlers from '../../../../frontend/js/shared/hooks/use-callback-handlers'
describe('useCallbackHandlers', function () {
it('adds, removes and calls all handlers without duplicate', async function () {
const handler1 = sinon.stub()
const handler2 = sinon.stub()
const handler3 = sinon.stub()
const { result } = renderHook(() => useCallbackHandlers())
result.current.addHandler(handler1)
result.current.deleteHandler(handler1)
result.current.addHandler(handler1)
result.current.addHandler(handler2)
result.current.deleteHandler(handler2)
result.current.addHandler(handler3)
result.current.addHandler(handler3)
result.current.callHandlers('foo')
result.current.callHandlers(1337)
sinon.assert.calledTwice(handler1)
sinon.assert.calledWith(handler1, 'foo')
sinon.assert.calledWith(handler1, 1337)
sinon.assert.notCalled(handler2)
sinon.assert.calledTwice(handler3)
})
})

View file

@ -0,0 +1,82 @@
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)
})
})

View file

@ -0,0 +1,187 @@
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 () {
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 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')
// 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. simulate closed detacher tab
sysendTestHelper.spy.broadcast.resetHistory()
sysendTestHelper.receiveMessage({
role: 'detacher',
event: 'closed',
})
expect(result.current.isLinked).to.be.true
expect(result.current.isLinking).to.be.false
expect(result.current.role).to.equal('detacher')
expect(sysendTestHelper.getLastBroacastMessage()).to.deep.equal({
role: 'detacher',
event: 'up',
})
// 6. 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('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)
})
})

View file

@ -0,0 +1,68 @@
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)
})
})

View file

@ -0,0 +1,53 @@
import { expect } from 'chai'
import sinon from 'sinon'
import { buildUrlWithDetachRole } from '../../../../frontend/js/shared/utils/url-helper'
describe('url-helper', function () {
let locationStub
describe('buildUrlWithDetachRole', function () {
beforeEach(function () {
locationStub = sinon.stub(window, 'location')
})
afterEach(function () {
locationStub.restore()
})
describe('without mode', function () {
it('removes trailing slash', function () {
locationStub.value('https://www.ovelreaf.com/project/1abc/')
expect(buildUrlWithDetachRole().href).to.equal(
'https://www.ovelreaf.com/project/1abc'
)
})
it('clears the mode from the current URL', function () {
locationStub.value('https://www.ovelreaf.com/project/2abc/detached')
expect(buildUrlWithDetachRole().href).to.equal(
'https://www.ovelreaf.com/project/2abc'
)
locationStub.value('https://www.ovelreaf.com/project/2abc/detacher/')
expect(buildUrlWithDetachRole().href).to.equal(
'https://www.ovelreaf.com/project/2abc'
)
})
})
describe('with mode', function () {
it('handles with trailing slash', function () {
locationStub.value('https://www.ovelreaf.com/project/3abc/')
expect(buildUrlWithDetachRole('detacher').href).to.equal(
'https://www.ovelreaf.com/project/3abc/detacher'
)
})
it('handles without trailing slash', function () {
locationStub.value('https://www.ovelreaf.com/project/4abc')
expect(buildUrlWithDetachRole('detached').href).to.equal(
'https://www.ovelreaf.com/project/4abc/detached'
)
})
})
})
})