mirror of
https://github.com/overleaf/overleaf.git
synced 2025-04-12 21:06:18 +00:00
Merge pull request #6053 from overleaf/ta-pdf-detach-tests
PDF Detach Misc Tests GitOrigin-RevId: 9615c8fdfd8964a9c63d7c91e4596d397a1d35dc
This commit is contained in:
parent
1201a733d9
commit
44eca312ff
16 changed files with 817 additions and 28 deletions
|
@ -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() {
|
||||
|
|
|
@ -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]
|
||||
)
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)',
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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: [] },
|
||||
})
|
||||
})
|
||||
})
|
|
@ -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',
|
||||
})
|
||||
})
|
||||
})
|
|
@ -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 },
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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}>
|
||||
|
|
41
services/web/test/frontend/helpers/sysend.js
Normal file
41
services/web/test/frontend/helpers/sysend.js
Normal 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,
|
||||
}
|
|
@ -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)
|
||||
})
|
||||
})
|
|
@ -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)
|
||||
})
|
||||
})
|
|
@ -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)
|
||||
})
|
||||
})
|
|
@ -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)
|
||||
})
|
||||
})
|
53
services/web/test/frontend/shared/utils/url-helper.test.js
Normal file
53
services/web/test/frontend/shared/utils/url-helper.test.js
Normal 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'
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
Loading…
Add table
Reference in a new issue