2021-10-21 06:31:51 -04:00
|
|
|
import PdfSynctexControls from '../../../../../frontend/js/features/pdf-preview/components/pdf-synctex-controls'
|
2021-12-14 08:24:53 -05:00
|
|
|
import { renderWithEditorContext } from '../../../helpers/render-with-context'
|
|
|
|
import sysendTestHelper from '../../../helpers/sysend'
|
2021-10-21 06:31:51 -04:00
|
|
|
import { cloneDeep } from 'lodash'
|
|
|
|
import fetchMock from 'fetch-mock'
|
2021-12-14 08:24:53 -05:00
|
|
|
import { fireEvent, screen, waitFor } from '@testing-library/react'
|
2021-10-21 06:31:51 -04:00
|
|
|
import fs from 'fs'
|
|
|
|
import path from 'path'
|
|
|
|
import { expect } from 'chai'
|
|
|
|
import { useCompileContext } from '../../../../../frontend/js/shared/context/compile-context'
|
|
|
|
import { useEffect } from 'react'
|
|
|
|
|
|
|
|
const examplePDF = path.join(__dirname, '../fixtures/test-example.pdf')
|
|
|
|
|
|
|
|
const scope = {
|
|
|
|
settings: {
|
|
|
|
syntaxValidation: false,
|
|
|
|
pdfViewer: 'pdfjs',
|
|
|
|
},
|
|
|
|
editor: {
|
|
|
|
sharejs_doc: {
|
|
|
|
doc_id: 'test-doc',
|
|
|
|
getSnapshot: () => 'some doc content',
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
const outputFiles = [
|
|
|
|
{
|
|
|
|
path: 'output.pdf',
|
|
|
|
build: '123',
|
|
|
|
url: '/build/output.pdf',
|
|
|
|
type: 'pdf',
|
|
|
|
},
|
|
|
|
{
|
|
|
|
path: 'output.log',
|
|
|
|
build: '123',
|
|
|
|
url: '/build/output.log',
|
|
|
|
type: 'log',
|
|
|
|
},
|
|
|
|
]
|
|
|
|
|
|
|
|
const mockCompile = () =>
|
|
|
|
fetchMock.post('express:/project/:projectId/compile', {
|
|
|
|
body: {
|
|
|
|
status: 'success',
|
|
|
|
clsiServerId: 'foo',
|
|
|
|
compileGroup: 'standard',
|
|
|
|
pdfDownloadDomain: 'https://clsi.test-overleaf.com',
|
|
|
|
outputFiles: cloneDeep(outputFiles),
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
const fileResponses = {
|
|
|
|
'/build/output.pdf': () => fs.createReadStream(examplePDF),
|
|
|
|
'/build/output.log': '',
|
|
|
|
}
|
|
|
|
|
|
|
|
const mockBuildFile = () =>
|
|
|
|
fetchMock.get('begin:https://clsi.test-overleaf.com/', _url => {
|
|
|
|
const url = new URL(_url, 'https://clsi.test-overleaf.com')
|
|
|
|
|
|
|
|
if (url.pathname in fileResponses) {
|
|
|
|
return fileResponses[url.pathname]
|
|
|
|
}
|
|
|
|
|
|
|
|
return 404
|
|
|
|
})
|
|
|
|
|
|
|
|
const mockHighlights = [
|
|
|
|
{
|
|
|
|
page: 1,
|
|
|
|
h: 85.03936,
|
|
|
|
v: 509.999878,
|
|
|
|
width: 441.921265,
|
|
|
|
height: 8.855677,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
page: 1,
|
|
|
|
h: 85.03936,
|
|
|
|
v: 486.089539,
|
|
|
|
width: 441.921265,
|
|
|
|
height: 8.855677,
|
|
|
|
},
|
|
|
|
]
|
|
|
|
|
|
|
|
const mockSynctex = () =>
|
|
|
|
fetchMock
|
|
|
|
.get('express:/project/:projectId/sync/code', () => {
|
|
|
|
return { pdf: cloneDeep(mockHighlights) }
|
|
|
|
})
|
|
|
|
.get('express:/project/:projectId/sync/pdf', () => {
|
|
|
|
return { code: [{ file: 'main.tex', line: 100 }] }
|
|
|
|
})
|
|
|
|
|
|
|
|
describe('<PdfSynctexControls/>', function () {
|
|
|
|
beforeEach(function () {
|
|
|
|
window.showNewPdfPreview = true
|
2021-12-14 08:24:53 -05:00
|
|
|
window.metaAttributesCache = new Map()
|
2021-10-21 06:31:51 -04:00
|
|
|
fetchMock.restore()
|
2021-12-14 08:24:53 -05:00
|
|
|
mockCompile()
|
|
|
|
mockSynctex()
|
|
|
|
mockBuildFile()
|
2021-10-21 06:31:51 -04:00
|
|
|
})
|
|
|
|
|
|
|
|
afterEach(function () {
|
|
|
|
window.showNewPdfPreview = undefined
|
2021-12-14 08:24:53 -05:00
|
|
|
window.metaAttributesCache = new Map()
|
2021-10-21 06:31:51 -04:00
|
|
|
fetchMock.restore()
|
|
|
|
})
|
|
|
|
|
|
|
|
it('handles clicks on sync buttons', async function () {
|
|
|
|
const Inner = () => {
|
|
|
|
const { setPosition } = useCompileContext()
|
|
|
|
|
|
|
|
// mock PDF scroll position update
|
|
|
|
useEffect(() => {
|
|
|
|
setPosition({
|
|
|
|
page: 1,
|
|
|
|
offset: { top: 10, left: 10 },
|
|
|
|
pageSize: { height: 500, width: 500 },
|
|
|
|
})
|
|
|
|
}, [setPosition])
|
|
|
|
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
|
2022-01-11 09:59:31 -05:00
|
|
|
const { container } = renderWithEditorContext(
|
2021-12-14 08:24:53 -05:00
|
|
|
<>
|
2021-10-21 06:31:51 -04:00
|
|
|
<Inner />
|
|
|
|
<PdfSynctexControls />
|
2021-12-14 08:24:53 -05:00
|
|
|
</>,
|
|
|
|
{ scope }
|
2021-10-21 06:31:51 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
const syncToPdfButton = await screen.findByRole('button', {
|
|
|
|
name: 'Go to code location in PDF',
|
|
|
|
})
|
|
|
|
|
|
|
|
const syncToCodeButton = await screen.findByRole('button', {
|
2022-01-11 09:59:46 -05:00
|
|
|
name: /Go to PDF location in code/,
|
2021-10-21 06:31:51 -04:00
|
|
|
})
|
|
|
|
|
2022-01-11 09:59:31 -05:00
|
|
|
expect(container.querySelectorAll('.synctex-control-icon').length).to.equal(
|
|
|
|
2
|
|
|
|
)
|
|
|
|
|
2021-10-21 06:31:51 -04:00
|
|
|
// mock editor cursor position update
|
|
|
|
fireEvent(
|
|
|
|
window,
|
|
|
|
new CustomEvent('cursor:editor:update', {
|
|
|
|
detail: { row: 100, column: 10 },
|
|
|
|
})
|
|
|
|
)
|
|
|
|
|
|
|
|
fireEvent.click(syncToPdfButton)
|
|
|
|
|
|
|
|
expect(syncToPdfButton.disabled).to.be.true
|
|
|
|
|
|
|
|
await waitFor(() => {
|
|
|
|
expect(fetchMock.called('express:/project/:projectId/sync/code')).to.be
|
|
|
|
.true
|
|
|
|
})
|
|
|
|
|
|
|
|
fireEvent.click(syncToCodeButton)
|
|
|
|
|
|
|
|
expect(syncToCodeButton.disabled).to.be.true
|
|
|
|
|
|
|
|
await waitFor(() => {
|
|
|
|
expect(fetchMock.called('express:/project/:projectId/sync/pdf')).to.be
|
|
|
|
.true
|
|
|
|
})
|
|
|
|
})
|
2021-12-14 08:24:53 -05:00
|
|
|
|
|
|
|
describe('with detacher role', async function () {
|
|
|
|
beforeEach(function () {
|
|
|
|
window.metaAttributesCache.set('ol-detachRole', 'detacher')
|
|
|
|
})
|
|
|
|
|
2022-01-11 09:59:31 -05:00
|
|
|
it('does not have go to PDF location button nor arrow icon', async function () {
|
|
|
|
const { container } = renderWithEditorContext(<PdfSynctexControls />, {
|
|
|
|
scope,
|
|
|
|
})
|
2021-12-14 08:24:53 -05:00
|
|
|
|
|
|
|
expect(
|
|
|
|
await screen.queryByRole('button', {
|
|
|
|
name: 'Go to PDF location in code',
|
|
|
|
})
|
|
|
|
).to.not.exist
|
2022-01-11 09:59:31 -05:00
|
|
|
|
|
|
|
expect(container.querySelector('.synctex-control-icon')).to.not.exist
|
2021-12-14 08:24:53 -05:00
|
|
|
})
|
|
|
|
|
|
|
|
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 () {
|
2022-01-11 09:59:31 -05:00
|
|
|
const { container } = renderWithEditorContext(<PdfSynctexControls />, {
|
|
|
|
scope,
|
|
|
|
})
|
2021-12-14 08:24:53 -05:00
|
|
|
sysendTestHelper.resetHistory()
|
|
|
|
|
|
|
|
const syncToPdfButton = await screen.findByRole('button', {
|
|
|
|
name: 'Go to code location in PDF',
|
|
|
|
})
|
|
|
|
|
2021-12-14 08:58:09 -05:00
|
|
|
// mock editor cursor position update
|
|
|
|
fireEvent(
|
|
|
|
window,
|
|
|
|
new CustomEvent('cursor:editor:update', {
|
|
|
|
detail: { row: 100, column: 10 },
|
|
|
|
})
|
|
|
|
)
|
|
|
|
|
2021-12-14 08:24:53 -05:00
|
|
|
sysendTestHelper.receiveMessage({
|
|
|
|
role: 'detached',
|
|
|
|
event: 'state-sync-to-pdf-inflight',
|
|
|
|
data: { value: true },
|
|
|
|
})
|
|
|
|
expect(syncToPdfButton.disabled).to.be.true
|
2022-01-11 09:59:31 -05:00
|
|
|
expect(container.querySelectorAll('.synctex-spin-icon').length).to.equal(
|
|
|
|
1
|
|
|
|
)
|
2021-12-14 08:24:53 -05:00
|
|
|
|
|
|
|
sysendTestHelper.receiveMessage({
|
|
|
|
role: 'detached',
|
|
|
|
event: 'state-sync-to-pdf-inflight',
|
|
|
|
data: { value: false },
|
|
|
|
})
|
|
|
|
expect(syncToPdfButton.disabled).to.be.false
|
2022-01-11 09:59:31 -05:00
|
|
|
expect(container.querySelectorAll('.synctex-spin-icon').length).to.equal(
|
|
|
|
0
|
|
|
|
)
|
2021-12-14 08:24:53 -05:00
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
describe('with detached role', async function () {
|
|
|
|
beforeEach(function () {
|
|
|
|
window.metaAttributesCache.set('ol-detachRole', 'detached')
|
|
|
|
})
|
|
|
|
|
2022-01-11 09:59:31 -05:00
|
|
|
it('does not have go to code location button nor arrow icon', async function () {
|
|
|
|
const { container } = renderWithEditorContext(<PdfSynctexControls />, {
|
|
|
|
scope,
|
|
|
|
})
|
2021-12-14 08:24:53 -05:00
|
|
|
|
|
|
|
expect(
|
|
|
|
await screen.queryByRole('button', {
|
|
|
|
name: 'Go to code location in PDF',
|
|
|
|
})
|
|
|
|
).to.not.exist
|
2022-01-11 09:59:31 -05:00
|
|
|
|
|
|
|
expect(container.querySelector('.synctex-control-icon')).to.not.exist
|
2021-12-14 08:24:53 -05:00
|
|
|
})
|
|
|
|
|
|
|
|
it('send go to code line action and update inflight state', async function () {
|
2022-01-11 09:59:31 -05:00
|
|
|
const { container } = renderWithEditorContext(<PdfSynctexControls />, {
|
|
|
|
scope,
|
|
|
|
})
|
2021-12-14 08:24:53 -05:00
|
|
|
sysendTestHelper.resetHistory()
|
|
|
|
|
|
|
|
const syncToCodeButton = await screen.findByRole('button', {
|
2022-01-11 09:59:46 -05:00
|
|
|
name: /Go to PDF location in code/,
|
2021-12-14 08:24:53 -05:00
|
|
|
})
|
|
|
|
|
|
|
|
sysendTestHelper.resetHistory()
|
|
|
|
|
2022-01-11 09:59:31 -05:00
|
|
|
expect(syncToCodeButton.disabled).to.be.false
|
|
|
|
expect(container.querySelectorAll('.synctex-spin-icon').length).to.equal(
|
|
|
|
0
|
|
|
|
)
|
|
|
|
|
2021-12-14 08:24:53 -05:00
|
|
|
fireEvent.click(syncToCodeButton)
|
|
|
|
|
|
|
|
expect(syncToCodeButton.disabled).to.be.true
|
2022-01-11 09:59:31 -05:00
|
|
|
expect(container.querySelectorAll('.synctex-spin-icon').length).to.equal(
|
|
|
|
1
|
|
|
|
)
|
2021-12-14 08:24:53 -05:00
|
|
|
|
|
|
|
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 },
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
2021-10-21 06:31:51 -04:00
|
|
|
})
|