diff --git a/services/web/app/views/project/editor/pdf.pug b/services/web/app/views/project/editor/pdf.pug
index ed4ac3b4a1..dc1a808399 100644
--- a/services/web/app/views/project/editor/pdf.pug
+++ b/services/web/app/views/project/editor/pdf.pug
@@ -8,7 +8,8 @@ div.full-size.pdf(ng-controller="PdfController")
isDraftModeOn: draft,
isSyntaxCheckOn: stop_on_validation_error,
lastCompileTimestamp: pdf.lastCompileTimestamp,
- logEntries: pdf.logEntries ? pdf.logEntries : {}
+ logEntries: pdf.logEntries ? pdf.logEntries : {},
+ rawLog: pdf.rawLog ? pdf.rawLog : ''
}`
on-clear-cache="clearCache"
on-recompile="recompile"
diff --git a/services/web/frontend/extracted-translation-keys.json b/services/web/frontend/extracted-translation-keys.json
index 5e3a25b57a..fa615b22a6 100644
--- a/services/web/frontend/extracted-translation-keys.json
+++ b/services/web/frontend/extracted-translation-keys.json
@@ -27,6 +27,8 @@
"off",
"on",
"other_output_files",
+ "raw_logs_description",
+ "raw_logs",
"recompile_from_scratch",
"recompile",
"run_syntax_check_now",
diff --git a/services/web/frontend/js/features/preview/components/preview-log-entry.js b/services/web/frontend/js/features/preview/components/preview-log-entry.js
index 192844bb41..42f72adc96 100644
--- a/services/web/frontend/js/features/preview/components/preview-log-entry.js
+++ b/services/web/frontend/js/features/preview/components/preview-log-entry.js
@@ -23,9 +23,12 @@ function PreviewLogEntry({
function handleLogEntryLinkClick() {
onLogEntryLocationClick({ file, line, column })
}
- const logEntryDescription = t('log_entry_description', {
- level: level
- })
+ const logEntryDescription =
+ level === 'raw'
+ ? t('raw_logs_description')
+ : t('log_entry_description', {
+ level: level
+ })
return (
{logEntries && logEntries.length > 0 ? (
@@ -16,12 +19,15 @@ function PreviewLogsPane({ logEntries, onLogEntryLocationClick }) {
) : (
No logs
)}
+
+
)
}
PreviewLogsPane.propTypes = {
logEntries: PropTypes.array,
+ rawLog: PropTypes.string,
onLogEntryLocationClick: PropTypes.func.isRequired
}
diff --git a/services/web/frontend/js/features/preview/components/preview-pane.js b/services/web/frontend/js/features/preview/components/preview-pane.js
index d7be97b551..582b65f5f5 100644
--- a/services/web/frontend/js/features/preview/components/preview-pane.js
+++ b/services/web/frontend/js/features/preview/components/preview-pane.js
@@ -100,6 +100,7 @@ function PreviewPane({
{showLogs ? (
) : null}
@@ -114,7 +115,8 @@ PreviewPane.propTypes = {
isDraftModeOn: PropTypes.bool.isRequired,
isSyntaxCheckOn: PropTypes.bool.isRequired,
lastCompileTimestamp: PropTypes.number,
- logEntries: PropTypes.object.isRequired
+ logEntries: PropTypes.object.isRequired,
+ rawLog: PropTypes.string
}),
onClearCache: PropTypes.func.isRequired,
onLogEntryLocationClick: PropTypes.func.isRequired,
diff --git a/services/web/frontend/stylesheets/app/editor/logs.less b/services/web/frontend/stylesheets/app/editor/logs.less
index 45e2873533..57ec9b2078 100644
--- a/services/web/frontend/stylesheets/app/editor/logs.less
+++ b/services/web/frontend/stylesheets/app/editor/logs.less
@@ -33,6 +33,10 @@
background-color: @ol-blue;
}
+.log-entry-header-raw {
+ background-color: @ol-blue-gray-4;
+}
+
.log-entry-header-title,
.log-entry-header-link {
font-family: @font-family-sans-serif;
diff --git a/services/web/locales/en.json b/services/web/locales/en.json
index 85e3dd625d..3d678ec06e 100644
--- a/services/web/locales/en.json
+++ b/services/web/locales/en.json
@@ -1,9 +1,11 @@
{
+ "raw_logs_description": "Raw logs from the LaTeX compiler",
+ "raw_logs": "Raw logs",
"first_error_popup_label": "Your project has errors. This is the first one.",
"dismiss_error_popup": "Dismiss first error alert",
"go_to_error_location": "Go to error location",
"view_all_errors": "View all errors",
- "log_entry_description": "Log entry with level: \"__level__\"",
+ "log_entry_description": "Log entry with level: __level__",
"navigate_log_source": "Navigate to log position in source code: __location__",
"other_output_files": "Other output files",
"toggle_output_files_list": "Toggle output files list",
diff --git a/services/web/test/frontend/features/preview/components/preview-log-entry.test.js b/services/web/test/frontend/features/preview/components/preview-log-entry.test.js
index 638c2140c9..ff6f32359c 100644
--- a/services/web/test/frontend/features/preview/components/preview-log-entry.test.js
+++ b/services/web/test/frontend/features/preview/components/preview-log-entry.test.js
@@ -7,13 +7,16 @@ import PreviewLogEntry from '../../../../../frontend/js/features/preview/compone
describe('', function() {
const level = 'error'
- const noOp = () => {}
describe('log entry description', function() {
- for (const level of ['error', 'warning', 'typesetting']) {
+ for (const level of ['error', 'warning', 'typesetting', 'raw']) {
it(`describes the log entry with ${level} information`, function() {
- render()
- screen.getByLabelText(`Log entry with level: "${level}"`)
+ render()
+ const expectedLabel =
+ level === 'raw'
+ ? 'Raw logs from the LaTeX compiler'
+ : `Log entry with level: ${level}`
+ screen.getByLabelText(expectedLabel)
})
}
})
@@ -29,34 +32,21 @@ describe('', function() {
})
it('renders both file and line', function() {
- render(
-
- )
+ render()
screen.getByRole('button', {
name: `Navigate to log position in source code: ${file}, ${line}`
})
})
it('renders only file when line information is not available', function() {
- render(
-
- )
+ render()
screen.getByRole('button', {
name: `Navigate to log position in source code: ${file}`
})
})
it('does not render when file information is not available', function() {
- render()
+ render()
expect(
screen.queryByRole('button', {
name: `Navigate to log position in source code: `
@@ -92,13 +82,7 @@ describe('', function() {
const logContent = 'foo bar latex error stuff baz'
it('renders collapsed contents by default', function() {
- render(
-
- )
+ render()
screen.getByText(logContent)
screen.getByRole('button', {
name: 'Expand'
@@ -106,13 +90,7 @@ describe('', function() {
})
it('supports expanding contents', function() {
- render(
-
- )
+ render()
screen.getByText(logContent)
const expandCollapseBtn = screen.getByRole('button', {
name: 'Expand'
@@ -124,9 +102,7 @@ describe('', function() {
})
it('should not render at all when there are no log contents', function() {
- const { container } = render(
-
- )
+ const { container } = render()
expect(container.querySelector('.log-entry-content')).to.not.exist
})
})
@@ -144,7 +120,6 @@ describe('', function() {
humanReadableHintComponent={logHint}
extraInfoURL={infoURL}
level={level}
- onLogEntryLocationClick={noOp}
/>
)
screen.getByText(logHintText)
@@ -157,7 +132,6 @@ describe('', function() {
humanReadableHintComponent={logHint}
extraInfoURL={infoURL}
level={level}
- onLogEntryLocationClick={noOp}
/>
)
screen.getByRole('link', { name: 'Learn more' })
@@ -169,7 +143,6 @@ describe('', function() {
content={logContent}
humanReadableHintComponent={logHint}
level={level}
- onLogEntryLocationClick={noOp}
/>
)
expect(screen.queryByRole('link', { name: 'Learn more' })).to.not.exist
diff --git a/services/web/test/frontend/features/preview/components/preview-logs-pane.test.js b/services/web/test/frontend/features/preview/components/preview-logs-pane.test.js
new file mode 100644
index 0000000000..fffe2cfe50
--- /dev/null
+++ b/services/web/test/frontend/features/preview/components/preview-logs-pane.test.js
@@ -0,0 +1,96 @@
+import React from 'react'
+import { screen, render, fireEvent } from '@testing-library/react'
+import PreviewLogsPane from '../../../../../frontend/js/features/preview/components/preview-logs-pane'
+import sinon from 'sinon'
+
+const { expect } = require('chai')
+
+describe('', function() {
+ const sampleError1 = {
+ content: 'error 1 content',
+ file: 'main.tex',
+ level: 'error',
+ line: 17,
+ message: 'Misplaced alignment tab character &.'
+ }
+ const sampleError2 = {
+ content: 'error 1 content',
+ file: 'main.tex',
+ level: 'error',
+ line: 22,
+ message: 'Extra alignment tab has been changed to cr.'
+ }
+ const sampleWarning = {
+ file: 'main.tex',
+ level: 'warning',
+ line: 30,
+ message: "Reference `idontexist' on page 1 undefined on input line 30."
+ }
+ const sampleRawLog = `
+ This is pdfTeX, Version 3.14159265-2.6-1.40.21 (TeX Live 2020) (preloaded format=pdflatex 2020.9.10) 6 NOV 2020 15:23
+entering extended mode
+ \\write18 enabled.
+ %&-line parsing enabled.
+**main.tex
+(/compile/main.tex
+ LaTeX2e <2020-02-02> patch level 5
+ L3 programming layer <2020-07-17> (/usr/local/texlive/2020/texmf-dist/tex/latex
+ /base/article.cls
+ Document Class: article 2019/12/20 v1.4l Standard LaTeX document class
+ (/usr/local/texlive/2020/texmf-dist/tex/latex/base/size10.clo
+ File: size10.clo 2019/12/20 v1.4l Standard LaTeX file (size option)
+ )`
+ const errors = [sampleError1, sampleError2]
+ const warnings = [sampleWarning]
+ const logEntries = [...errors, ...warnings]
+
+ const onLogEntryLocationClick = sinon.stub()
+
+ beforeEach(function() {
+ render(
+
+ )
+ })
+ it('renders all log entries with appropriate labels', function() {
+ const errorEntries = screen.getAllByLabelText(`Log entry with level: error`)
+ const warningEntries = screen.getAllByLabelText(
+ `Log entry with level: warning`
+ )
+ expect(errorEntries).to.have.lengthOf(errors.length)
+ expect(warningEntries).to.have.lengthOf(warnings.length)
+ })
+
+ it('renders the raw log', function() {
+ screen.getByLabelText('Raw logs from the LaTeX compiler')
+ })
+
+ it('renders a link to location button for every error and warning log entry', function() {
+ logEntries.forEach((entry, index) => {
+ const linkToSourceButton = screen.getByRole('button', {
+ name: `Navigate to log position in source code: ${entry.file}, ${
+ entry.line
+ }`
+ })
+ fireEvent.click(linkToSourceButton)
+ expect(onLogEntryLocationClick).to.have.callCount(index + 1)
+ const call = onLogEntryLocationClick.getCall(index)
+ expect(
+ call.calledWith({
+ file: entry.file,
+ line: entry.line,
+ column: entry.column
+ })
+ ).to.be.true
+ })
+ })
+ it('does not render a link to location button for the raw log entry', function() {
+ const rawLogEntry = screen.getByLabelText(
+ 'Raw logs from the LaTeX compiler'
+ )
+ expect(rawLogEntry.querySelector('.log-entry-header-link')).to.not.exist
+ })
+})