diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-logs-entries.js b/services/web/frontend/js/features/pdf-preview/components/pdf-logs-entries.js
index 1675243397..04a92e3532 100644
--- a/services/web/frontend/js/features/pdf-preview/components/pdf-logs-entries.js
+++ b/services/web/frontend/js/features/pdf-preview/components/pdf-logs-entries.js
@@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next'
import PreviewLogsPaneMaxEntries from '../../preview/components/preview-logs-pane-max-entries'
import PdfLogEntry from './pdf-log-entry'
import { useIdeContext } from '../../../shared/context/ide-context'
+import useDetachAction from '../../../shared/hooks/use-detach-action'
const LOG_PREVIEW_LIMIT = 100
@@ -12,7 +13,7 @@ function PdfLogsEntries({ entries, hasErrors }) {
const ide = useIdeContext()
- const syncToEntry = useCallback(
+ const _syncToEntry = useCallback(
entry => {
const entity = ide.fileTreeManager.findEntityByPath(entry.file)
@@ -26,6 +27,13 @@ function PdfLogsEntries({ entries, hasErrors }) {
[ide]
)
+ const syncToEntry = useDetachAction(
+ 'sync-to-entry',
+ _syncToEntry,
+ 'detached',
+ 'detacher'
+ )
+
const logEntries = entries.slice(0, LOG_PREVIEW_LIMIT)
return (
diff --git a/services/web/test/frontend/features/pdf-preview/components/pdf-logs-entries.test.js b/services/web/test/frontend/features/pdf-preview/components/pdf-logs-entries.test.js
new file mode 100644
index 0000000000..3995c994e5
--- /dev/null
+++ b/services/web/test/frontend/features/pdf-preview/components/pdf-logs-entries.test.js
@@ -0,0 +1,114 @@
+import PdfLogsEntries from '../../../../../frontend/js/features/pdf-preview/components/pdf-logs-entries'
+import { renderWithEditorContext } from '../../../helpers/render-with-context'
+import { screen, fireEvent } from '@testing-library/react'
+import sysendTestHelper from '../../../helpers/sysend'
+import { expect } from 'chai'
+import sinon from 'sinon'
+
+describe('', function () {
+ const fileTreeManager = {}
+ const editorManager = {}
+ const logEntries = [
+ {
+ file: 'main.tex',
+ line: 9,
+ column: 8,
+ level: 'error',
+ message: 'LaTeX Error',
+ content: 'See the LaTeX manual',
+ raw: '',
+ ruleId: 'latex_error',
+ humanReadableHint: '',
+ humanReadableHintComponent: <>>,
+ key: '',
+ },
+ ]
+ const fakeEntity = { type: 'doc' }
+
+ beforeEach(function () {
+ fileTreeManager.findEntityByPath = sinon.stub().returns(fakeEntity)
+ editorManager.openDoc = sinon.stub()
+ })
+
+ afterEach(function () {
+ window.metaAttributesCache = new Map()
+ sysendTestHelper.resetHistory()
+ fileTreeManager.findEntityByPath.resetHistory()
+ })
+
+ it('opens doc on click', async function () {
+ renderWithEditorContext(, {
+ fileTreeManager,
+ editorManager,
+ })
+
+ const button = await screen.getByRole('button', {
+ name: 'Navigate to log position in source code: main.tex, 9',
+ })
+ fireEvent.click(button)
+ sinon.assert.calledOnce(fileTreeManager.findEntityByPath)
+ sinon.assert.calledOnce(editorManager.openDoc)
+ sinon.assert.calledWith(editorManager.openDoc, fakeEntity, {
+ gotoLine: 9,
+ gotoColumn: 8,
+ })
+ })
+
+ it('opens doc via detached action', async function () {
+ window.metaAttributesCache.set('ol-detachRole', 'detacher')
+
+ renderWithEditorContext(, {
+ fileTreeManager,
+ editorManager,
+ })
+
+ sysendTestHelper.receiveMessage({
+ role: 'detached',
+ event: 'action-sync-to-entry',
+ data: {
+ args: [
+ {
+ file: 'main.tex',
+ line: 7,
+ column: 6,
+ },
+ ],
+ },
+ })
+
+ sinon.assert.calledOnce(fileTreeManager.findEntityByPath)
+ sinon.assert.calledOnce(editorManager.openDoc)
+ sinon.assert.calledWith(editorManager.openDoc, fakeEntity, {
+ gotoLine: 7,
+ gotoColumn: 6,
+ })
+ })
+
+ it('sends open doc clicks via detached action', async function () {
+ window.metaAttributesCache.set('ol-detachRole', 'detached')
+ renderWithEditorContext(, {
+ fileTreeManager,
+ editorManager,
+ })
+
+ const button = await screen.getByRole('button', {
+ name: 'Navigate to log position in source code: main.tex, 9',
+ })
+ fireEvent.click(button)
+ sinon.assert.notCalled(fileTreeManager.findEntityByPath)
+ sinon.assert.notCalled(editorManager.openDoc)
+ expect(sysendTestHelper.getLastBroacastMessage()).to.deep.equal({
+ role: 'detached',
+ event: 'action-sync-to-entry',
+ data: {
+ args: [
+ {
+ file: 'main.tex',
+ line: 9,
+ column: 8,
+ },
+ ],
+ },
+ })
+ })
+})
diff --git a/services/web/test/frontend/helpers/render-with-context.js b/services/web/test/frontend/helpers/render-with-context.js
index baab046538..9cd792532c 100644
--- a/services/web/test/frontend/helpers/render-with-context.js
+++ b/services/web/test/frontend/helpers/render-with-context.js
@@ -33,6 +33,17 @@ export function EditorProviders({
children,
rootFolder,
ui = { view: null, pdfLayout: 'flat', chatOpen: true },
+ fileTreeManager = {
+ findEntityById: () => null,
+ findEntityByPath: () => null,
+ getEntityPath: () => '',
+ getRootDocDirname: () => '',
+ },
+ editorManager = {
+ getCurrentDocId: () => 'foo',
+ getCurrentDocValue: () => {},
+ openDoc: sinon.stub(),
+ },
}) {
window.user = user || window.user
window.gitBridgePublicBaseUrl = 'git.overleaf.test'
@@ -66,19 +77,6 @@ export function EditorProviders({
...scope,
}
- const fileTreeManager = {
- findEntityById: () => null,
- findEntityByPath: () => null,
- getEntityPath: () => '',
- getRootDocDirname: () => '',
- }
-
- const editorManager = {
- getCurrentDocId: () => 'foo',
- getCurrentDocValue: () => {},
- openDoc: sinon.stub(),
- }
-
const metadataManager = {
metadata: {
state: {