mirror of
https://github.com/overleaf/overleaf.git
synced 2024-09-16 02:52:31 -04:00
Refactor compile-related code from PDF preview context provider into a separate class (#5341)
GitOrigin-RevId: 96b8bb527fa3d60a5fb84eee2b8f4fabc1726875
This commit is contained in:
parent
515180aeaa
commit
b902bd9265
9 changed files with 403 additions and 386 deletions
|
@ -3,7 +3,7 @@ import Icon from '../../../shared/components/icon'
|
|||
import ControlledDropdown from '../../../shared/components/controlled-dropdown'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { usePdfPreviewContext } from '../contexts/pdf-preview-context'
|
||||
import { memo, useCallback } from 'react'
|
||||
import { memo } from 'react'
|
||||
import classnames from 'classnames'
|
||||
|
||||
function PdfCompileButton() {
|
||||
|
@ -12,10 +12,10 @@ function PdfCompileButton() {
|
|||
compiling,
|
||||
draft,
|
||||
hasChanges,
|
||||
recompile,
|
||||
setAutoCompile,
|
||||
setDraft,
|
||||
setStopOnValidationError,
|
||||
startCompile,
|
||||
stopCompile,
|
||||
stopOnValidationError,
|
||||
recompileFromScratch,
|
||||
|
@ -25,10 +25,6 @@ function PdfCompileButton() {
|
|||
|
||||
const compileButtonLabel = compiling ? t('compiling') + '…' : t('recompile')
|
||||
|
||||
const startCompile = useCallback(() => {
|
||||
recompile()
|
||||
}, [recompile])
|
||||
|
||||
return (
|
||||
<ControlledDropdown
|
||||
className={classnames({
|
||||
|
|
|
@ -6,7 +6,7 @@ import { memo } from 'react'
|
|||
export function PdfLogsButtonContent({
|
||||
showLogs,
|
||||
logEntries,
|
||||
autoCompileLintingError,
|
||||
codeCheckFailed,
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
|
@ -19,7 +19,7 @@ export function PdfLogsButtonContent({
|
|||
)
|
||||
}
|
||||
|
||||
if (autoCompileLintingError) {
|
||||
if (codeCheckFailed) {
|
||||
return (
|
||||
<>
|
||||
<Icon type="exclamation-triangle" />
|
||||
|
@ -59,7 +59,7 @@ export function PdfLogsButtonContent({
|
|||
}
|
||||
|
||||
PdfLogsButtonContent.propTypes = {
|
||||
autoCompileLintingError: PropTypes.bool,
|
||||
codeCheckFailed: PropTypes.bool,
|
||||
showLogs: PropTypes.bool,
|
||||
logEntries: PropTypes.object,
|
||||
}
|
||||
|
|
|
@ -6,8 +6,7 @@ import { sendMBOnce } from '../../../infrastructure/event-tracking'
|
|||
|
||||
function PdfLogsButton() {
|
||||
const {
|
||||
autoCompileLintingError,
|
||||
stopOnValidationError,
|
||||
codeCheckFailed,
|
||||
error,
|
||||
logEntries,
|
||||
showLogs,
|
||||
|
@ -19,7 +18,7 @@ function PdfLogsButton() {
|
|||
return 'default'
|
||||
}
|
||||
|
||||
if (autoCompileLintingError && stopOnValidationError) {
|
||||
if (codeCheckFailed) {
|
||||
return 'danger'
|
||||
}
|
||||
|
||||
|
@ -34,7 +33,7 @@ function PdfLogsButton() {
|
|||
}
|
||||
|
||||
return 'default'
|
||||
}, [autoCompileLintingError, logEntries, showLogs, stopOnValidationError])
|
||||
}, [codeCheckFailed, logEntries, showLogs])
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
setShowLogs(value => {
|
||||
|
@ -57,9 +56,7 @@ function PdfLogsButton() {
|
|||
<PdfLogsButtonContent
|
||||
showLogs={showLogs}
|
||||
logEntries={logEntries}
|
||||
autoCompileLintingError={
|
||||
autoCompileLintingError && stopOnValidationError
|
||||
}
|
||||
codeCheckFailed={codeCheckFailed}
|
||||
/>
|
||||
</Button>
|
||||
)
|
||||
|
|
|
@ -14,8 +14,7 @@ import ErrorBoundaryFallback from './error-boundary-fallback'
|
|||
|
||||
function PdfLogsViewer() {
|
||||
const {
|
||||
autoCompileLintingError,
|
||||
stopOnValidationError,
|
||||
codeCheckFailed,
|
||||
error,
|
||||
logEntries,
|
||||
rawLog,
|
||||
|
@ -27,7 +26,7 @@ function PdfLogsViewer() {
|
|||
return (
|
||||
<div className="logs-pane">
|
||||
<div className="logs-pane-content">
|
||||
{autoCompileLintingError && stopOnValidationError && (
|
||||
{codeCheckFailed && (
|
||||
<div className="log-entry">
|
||||
<div className="log-entry-header log-entry-header-error">
|
||||
<div className="log-entry-header-icon-container">
|
||||
|
|
|
@ -9,32 +9,20 @@ import {
|
|||
import PropTypes from 'prop-types'
|
||||
import useScopeValue from '../../../shared/context/util/scope-value-hook'
|
||||
import { useProjectContext } from '../../../shared/context/project-context'
|
||||
import getMeta from '../../../utils/meta'
|
||||
import { deleteJSON, postJSON } from '../../../infrastructure/fetch-json'
|
||||
import usePersistedState from '../../../shared/hooks/use-persisted-state'
|
||||
import {
|
||||
buildLogEntryAnnotations,
|
||||
handleOutputFiles,
|
||||
} from '../util/output-files'
|
||||
import { debounce } from 'lodash'
|
||||
import { useIdeContext } from '../../../shared/context/ide-context'
|
||||
import {
|
||||
send,
|
||||
sendMB,
|
||||
sendMBSampled,
|
||||
} from '../../../infrastructure/event-tracking'
|
||||
import { useEditorContext } from '../../../shared/context/editor-context'
|
||||
import { isMainFile } from '../util/editor-files'
|
||||
import useAbortController from '../../../shared/hooks/use-abort-controller'
|
||||
|
||||
const AUTO_COMPILE_MAX_WAIT = 5000
|
||||
// We add a 1 second debounce to sending user changes to server if they aren't
|
||||
// collaborating with anyone. This needs to be higher than that, and allow for
|
||||
// client to server latency, otherwise we compile before the op reaches the server
|
||||
// and then again on ack.
|
||||
const AUTO_COMPILE_DEBOUNCE = 2000
|
||||
|
||||
const searchParams = new URLSearchParams(window.location.search)
|
||||
import DocumentCompiler from '../util/compiler'
|
||||
import { useIdeContext } from '../../../shared/context/ide-context'
|
||||
|
||||
export const PdfPreviewContext = createContext(undefined)
|
||||
|
||||
|
@ -45,7 +33,9 @@ PdfPreviewProvider.propTypes = {
|
|||
export default function PdfPreviewProvider({ children }) {
|
||||
const ide = useIdeContext()
|
||||
|
||||
const { _id: projectId, rootDoc_id: rootDocId } = useProjectContext()
|
||||
const project = useProjectContext()
|
||||
|
||||
const projectId = project._id
|
||||
|
||||
const { hasPremiumCompile, isProjectOwner } = useEditorContext()
|
||||
|
||||
|
@ -65,10 +55,7 @@ export default function PdfPreviewProvider({ children }) {
|
|||
const [, setLogEntryAnnotations] = useScopeValue('pdf.logEntryAnnotations')
|
||||
|
||||
// the id of the CLSI server which ran the compile
|
||||
const [clsiServerId, setClsiServerId] = useScopeValue('ide.clsiServerId')
|
||||
|
||||
// the compile group (standard or priority)
|
||||
const [compileGroup, setCompileGroup] = useScopeValue('ide.compileGroup')
|
||||
const [, setClsiServerId] = useScopeValue('ide.clsiServerId')
|
||||
|
||||
// whether to display the editor and preview side-by-side or full-width ("flat")
|
||||
const [pdfLayout, setPdfLayout] = useScopeValue('ui.pdfLayout')
|
||||
|
@ -79,6 +66,9 @@ export default function PdfPreviewProvider({ children }) {
|
|||
// whether a compile is in progress
|
||||
const [compiling, setCompiling] = useState(false)
|
||||
|
||||
// data received in response to a compile request
|
||||
const [data, setData] = useState()
|
||||
|
||||
// whether the project has been compiled yet
|
||||
const [compiledOnce, setCompiledOnce] = useState(false)
|
||||
|
||||
|
@ -117,10 +107,7 @@ export default function PdfPreviewProvider({ children }) {
|
|||
true
|
||||
)
|
||||
|
||||
// the id of the currently open document in the editor
|
||||
const [currentDocId] = useScopeValue('editor.open_doc_id')
|
||||
|
||||
// the Document currently open in the editor?
|
||||
// the Document currently open in the editor
|
||||
const [currentDoc] = useScopeValue('editor.sharejs_doc')
|
||||
|
||||
// whether the PDF view is hidden
|
||||
|
@ -137,6 +124,35 @@ export default function PdfPreviewProvider({ children }) {
|
|||
|
||||
const { signal } = useAbortController()
|
||||
|
||||
// the document compiler
|
||||
const [compiler] = useState(() => {
|
||||
return new DocumentCompiler({
|
||||
project,
|
||||
setChangedAt,
|
||||
setCompiling,
|
||||
setData,
|
||||
setError,
|
||||
signal,
|
||||
})
|
||||
})
|
||||
|
||||
// clean up the compiler on unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
compiler.destroy()
|
||||
}
|
||||
}, [compiler])
|
||||
|
||||
// keep currentDoc in sync with the compiler
|
||||
useEffect(() => {
|
||||
compiler.currentDoc = currentDoc
|
||||
}, [compiler, currentDoc])
|
||||
|
||||
// keep draft setting in sync with the compiler
|
||||
useEffect(() => {
|
||||
compiler.draft = draft
|
||||
}, [compiler, draft])
|
||||
|
||||
// pass the "uncompiled" value up into the scope for use outside this context provider
|
||||
useEffect(() => {
|
||||
setUncompiled(changedAt > 0)
|
||||
|
@ -151,46 +167,26 @@ export default function PdfPreviewProvider({ children }) {
|
|||
[_setAutoCompile]
|
||||
)
|
||||
|
||||
// parse the text of the current doc in the editor
|
||||
// if it contains "\documentclass" then use this as the root doc
|
||||
const getRootDocOverrideId = useCallback(() => {
|
||||
if (currentDocId === rootDocId) {
|
||||
return null // no need to override when in the root doc itself
|
||||
// always compile the PDF once after opening the project, after the doc has loaded
|
||||
useEffect(() => {
|
||||
if (!compiledOnce && currentDoc) {
|
||||
setCompiledOnce(true)
|
||||
compiler.compile({ isAutoCompileOnLoad: true })
|
||||
}
|
||||
|
||||
if (currentDoc) {
|
||||
const doc = currentDoc.getSnapshot()
|
||||
|
||||
if (doc) {
|
||||
return isMainFile(doc)
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}, [currentDoc, currentDocId, rootDocId])
|
||||
|
||||
// TODO: remove this?
|
||||
const sendCompileMetrics = useCallback(() => {
|
||||
if (compiledOnce && !error && !window.user.alphaProgram) {
|
||||
const metadata = {
|
||||
errors: logEntries.errors.length,
|
||||
warnings: logEntries.warnings.length,
|
||||
typesetting: logEntries.typesetting.length,
|
||||
newPdfPreview: true,
|
||||
}
|
||||
sendMBSampled('compile-result', metadata, 0.01)
|
||||
}
|
||||
}, [compiledOnce, error, logEntries])
|
||||
}, [compiledOnce, currentDoc, compiler])
|
||||
|
||||
// handle the data returned from a compile request
|
||||
const handleCompileData = useCallback(
|
||||
(data, options) => {
|
||||
// note: this should _only_ run when `data` changes,
|
||||
// the other dependencies must all be static
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
if (data.clsiServerId) {
|
||||
setClsiServerId(data.clsiServerId)
|
||||
setClsiServerId(data.clsiServerId) // set in scope, for PdfSynctexController
|
||||
compiler.clsiServerId = data.clsiServerId
|
||||
}
|
||||
|
||||
if (data.compileGroup) {
|
||||
setCompileGroup(data.compileGroup)
|
||||
compiler.compileGroup = data.compileGroup
|
||||
}
|
||||
|
||||
if (data.outputFiles) {
|
||||
|
@ -203,13 +199,27 @@ export default function PdfPreviewProvider({ children }) {
|
|||
setPdfDownloadUrl(result.pdfDownloadUrl)
|
||||
setPdfUrl(result.pdfUrl)
|
||||
setRawLog(result.log)
|
||||
|
||||
// sample compile stats for real users
|
||||
if (!window.user.alphaProgram && data.status === 'success') {
|
||||
sendMBSampled(
|
||||
'compile-result',
|
||||
{
|
||||
errors: result.logEntries.errors.length,
|
||||
warnings: result.logEntries.warnings.length,
|
||||
typesetting: result.logEntries.typesetting.length,
|
||||
newPdfPreview: true, // TODO: is this useful?
|
||||
},
|
||||
0.01
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
switch (data.status) {
|
||||
case 'success':
|
||||
setError(undefined)
|
||||
setShowLogs(false) // TODO: always?
|
||||
setShowLogs(false)
|
||||
break
|
||||
|
||||
case 'clsi-maintenance':
|
||||
|
@ -238,7 +248,7 @@ export default function PdfPreviewProvider({ children }) {
|
|||
break
|
||||
|
||||
case 'autocompile-backoff':
|
||||
if (!options.isAutoCompileOnLoad) {
|
||||
if (!data.options.isAutoCompileOnLoad) {
|
||||
setError('autocompile-disabled')
|
||||
setAutoCompile(false)
|
||||
sendMB('autocompile-rate-limited', { hasPremiumCompile })
|
||||
|
@ -258,97 +268,21 @@ export default function PdfPreviewProvider({ children }) {
|
|||
setError('error')
|
||||
break
|
||||
}
|
||||
},
|
||||
[
|
||||
hasPremiumCompile,
|
||||
ide.fileTreeManager,
|
||||
isProjectOwner,
|
||||
projectId,
|
||||
setAutoCompile,
|
||||
setClsiServerId,
|
||||
setCompileGroup,
|
||||
setLogEntries,
|
||||
setLogEntryAnnotations,
|
||||
setPdfDownloadUrl,
|
||||
setPdfUrl,
|
||||
]
|
||||
)
|
||||
|
||||
const buildCompileParams = useCallback(
|
||||
options => {
|
||||
const params = new URLSearchParams()
|
||||
|
||||
if (clsiServerId) {
|
||||
params.set('clsiserverid', clsiServerId)
|
||||
}
|
||||
|
||||
if (options.isAutoCompileOnLoad || options.isAutoCompileOnChange) {
|
||||
params.set('auto_compile', 'true')
|
||||
}
|
||||
|
||||
if (getMeta('ol-enablePdfCaching')) {
|
||||
params.set('enable_pdf_caching', 'true')
|
||||
}
|
||||
|
||||
if (searchParams.get('file_line_errors') === 'true') {
|
||||
params.file_line_errors = 'true'
|
||||
}
|
||||
|
||||
return params
|
||||
},
|
||||
[clsiServerId]
|
||||
)
|
||||
|
||||
// run a compile
|
||||
const recompile = useCallback(
|
||||
(options = {}) => {
|
||||
if (compiling) {
|
||||
return
|
||||
}
|
||||
|
||||
sendMBSampled('editor-recompile-sampled', options)
|
||||
|
||||
setChangedAt(0) // NOTE: this sets uncompiled to false
|
||||
setCompiling(true)
|
||||
setValidationIssues(undefined)
|
||||
|
||||
window.dispatchEvent(new CustomEvent('flush-changes')) // TODO: wait for this?
|
||||
|
||||
postJSON(`/project/${projectId}/compile?${buildCompileParams(options)}`, {
|
||||
body: {
|
||||
rootDoc_id: getRootDocOverrideId(),
|
||||
draft,
|
||||
check: 'silent', // NOTE: 'error' and 'validate' are possible, but unused
|
||||
// use incremental compile for all users but revert to a full compile
|
||||
// if there was previously a server error
|
||||
incrementalCompilesEnabled: !error,
|
||||
},
|
||||
signal,
|
||||
})
|
||||
.then(data => {
|
||||
handleCompileData(data, options)
|
||||
})
|
||||
.catch(error => {
|
||||
// console.error(error)
|
||||
setError(error.info?.statusCode === 429 ? 'rate-limited' : 'error')
|
||||
})
|
||||
.finally(() => {
|
||||
setCompiling(false)
|
||||
sendCompileMetrics()
|
||||
})
|
||||
},
|
||||
[
|
||||
compiling,
|
||||
projectId,
|
||||
buildCompileParams,
|
||||
getRootDocOverrideId,
|
||||
draft,
|
||||
error,
|
||||
handleCompileData,
|
||||
sendCompileMetrics,
|
||||
signal,
|
||||
]
|
||||
)
|
||||
}
|
||||
}, [
|
||||
compiler,
|
||||
data,
|
||||
ide,
|
||||
hasPremiumCompile,
|
||||
isProjectOwner,
|
||||
projectId,
|
||||
setAutoCompile,
|
||||
setClsiServerId,
|
||||
setLogEntries,
|
||||
setLogEntryAnnotations,
|
||||
setPdfDownloadUrl,
|
||||
setPdfUrl,
|
||||
])
|
||||
|
||||
// switch to logs if there's an error
|
||||
useEffect(() => {
|
||||
|
@ -360,7 +294,7 @@ export default function PdfPreviewProvider({ children }) {
|
|||
// recompile on key press
|
||||
useEffect(() => {
|
||||
const listener = event => {
|
||||
recompile(event.detail)
|
||||
compiler.compile(event.detail)
|
||||
}
|
||||
|
||||
window.addEventListener('pdf:recompile', listener)
|
||||
|
@ -368,73 +302,40 @@ export default function PdfPreviewProvider({ children }) {
|
|||
return () => {
|
||||
window.removeEventListener('pdf:recompile', listener)
|
||||
}
|
||||
}, [recompile])
|
||||
|
||||
// always compile the PDF once, when joining the project
|
||||
useEffect(() => {
|
||||
const listener = () => {
|
||||
if (!compiledOnce) {
|
||||
setCompiledOnce(true)
|
||||
recompile({ isAutoCompileOnLoad: true })
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('project:joined', listener)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('project:joined', listener)
|
||||
}
|
||||
}, [compiledOnce, recompile])
|
||||
}, [compiler])
|
||||
|
||||
// whether there has been an autocompile linting error, if syntax validation is switched on
|
||||
const autoCompileLintingError = Boolean(
|
||||
autoCompile && syntaxValidation && hasLintingError
|
||||
)
|
||||
|
||||
// the project has visible changes
|
||||
const codeCheckFailed = stopOnValidationError && autoCompileLintingError
|
||||
|
||||
// show that the project has pending changes
|
||||
const hasChanges = Boolean(
|
||||
autoCompile &&
|
||||
uncompiled &&
|
||||
compiledOnce &&
|
||||
!(stopOnValidationError && autoCompileLintingError)
|
||||
autoCompile && uncompiled && compiledOnce && !codeCheckFailed
|
||||
)
|
||||
|
||||
// the project is available for auto-compiling
|
||||
const canAutoCompile = Boolean(
|
||||
autoCompile &&
|
||||
!compiling &&
|
||||
!pdfHidden &&
|
||||
!(stopOnValidationError && autoCompileLintingError)
|
||||
autoCompile && !compiling && !pdfHidden && !codeCheckFailed
|
||||
)
|
||||
|
||||
// a debounced wrapper around the recompile function, used for auto-compile
|
||||
const [debouncedAutoCompile] = useState(() => {
|
||||
return debounce(
|
||||
() => {
|
||||
recompile({ isAutoCompileOnChange: true })
|
||||
},
|
||||
AUTO_COMPILE_DEBOUNCE,
|
||||
{
|
||||
maxWait: AUTO_COMPILE_MAX_WAIT,
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
// call the debounced recompile function if the project is available for auto-compiling and it has changed
|
||||
// call the debounced autocompile function if the project is available for auto-compiling and it has changed
|
||||
useEffect(() => {
|
||||
if (canAutoCompile && changedAt > 0) {
|
||||
debouncedAutoCompile()
|
||||
compiler.debouncedAutoCompile()
|
||||
} else {
|
||||
debouncedAutoCompile.cancel()
|
||||
compiler.debouncedAutoCompile.cancel()
|
||||
}
|
||||
}, [canAutoCompile, debouncedAutoCompile, recompile, changedAt])
|
||||
}, [compiler, canAutoCompile, changedAt])
|
||||
|
||||
// cancel debounced recompile on unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
debouncedAutoCompile.cancel()
|
||||
compiler.debouncedAutoCompile.cancel()
|
||||
}
|
||||
}, [debouncedAutoCompile])
|
||||
}, [compiler])
|
||||
|
||||
// record doc changes when notified by the editor
|
||||
useEffect(() => {
|
||||
|
@ -451,52 +352,31 @@ export default function PdfPreviewProvider({ children }) {
|
|||
}
|
||||
}, [])
|
||||
|
||||
// send a request to stop the current compile
|
||||
// start a compile manually
|
||||
const startCompile = useCallback(() => {
|
||||
compiler.compile()
|
||||
}, [compiler])
|
||||
|
||||
// stop a compile manually
|
||||
const stopCompile = useCallback(() => {
|
||||
// TODO: stoppingCompile state?
|
||||
|
||||
const params = new URLSearchParams()
|
||||
|
||||
if (clsiServerId) {
|
||||
params.set('clsiserverid', clsiServerId)
|
||||
}
|
||||
|
||||
return postJSON(`/project/${projectId}/compile/stop?${params}`, { signal })
|
||||
.catch(error => {
|
||||
setError(error)
|
||||
})
|
||||
.finally(() => {
|
||||
setCompiling(false)
|
||||
})
|
||||
}, [projectId, clsiServerId, signal])
|
||||
compiler.stopCompile()
|
||||
}, [compiler])
|
||||
|
||||
// clear the compile cache
|
||||
const clearCache = useCallback(() => {
|
||||
setClearingCache(true)
|
||||
|
||||
const params = new URLSearchParams()
|
||||
|
||||
if (clsiServerId) {
|
||||
params.set('clsiserverid', clsiServerId)
|
||||
}
|
||||
|
||||
return deleteJSON(`/project/${projectId}/output?${params}`, { signal })
|
||||
.catch(error => {
|
||||
console.error(error)
|
||||
setError('clear-cache')
|
||||
})
|
||||
.finally(() => {
|
||||
setClearingCache(false)
|
||||
})
|
||||
}, [clsiServerId, projectId, setError, signal])
|
||||
return compiler.clearCache().finally(() => {
|
||||
setClearingCache(false)
|
||||
})
|
||||
}, [compiler])
|
||||
|
||||
// clear the cache then run a compile, triggered by a menu item
|
||||
const recompileFromScratch = useCallback(() => {
|
||||
setClearingCache(true)
|
||||
clearCache().then(() => {
|
||||
setClearingCache(false)
|
||||
recompile()
|
||||
compiler.compile()
|
||||
})
|
||||
}, [clearCache, recompile])
|
||||
}, [clearCache, compiler])
|
||||
|
||||
// switch to either side-by-side or flat (full-width) layout
|
||||
const switchLayout = useCallback(() => {
|
||||
|
@ -512,12 +392,9 @@ export default function PdfPreviewProvider({ children }) {
|
|||
const value = useMemo(() => {
|
||||
return {
|
||||
autoCompile,
|
||||
autoCompileLintingError,
|
||||
codeCheckFailed,
|
||||
clearCache,
|
||||
clearingCache,
|
||||
clsiServerId,
|
||||
compileGroup,
|
||||
compiledOnce,
|
||||
compiling,
|
||||
draft,
|
||||
error,
|
||||
|
@ -529,23 +406,14 @@ export default function PdfPreviewProvider({ children }) {
|
|||
pdfLayout,
|
||||
pdfUrl,
|
||||
rawLog,
|
||||
recompile,
|
||||
recompileFromScratch,
|
||||
setAutoCompile,
|
||||
setClsiServerId,
|
||||
setCompileGroup,
|
||||
setCompiledOnce,
|
||||
setDraft,
|
||||
setError,
|
||||
setHasLintingError, // for story
|
||||
setLogEntries,
|
||||
setPdfDownloadUrl,
|
||||
setPdfLayout,
|
||||
setPdfUrl,
|
||||
setHasLintingError, // only for stories
|
||||
setShowLogs,
|
||||
setStopOnValidationError,
|
||||
setUiView,
|
||||
showLogs,
|
||||
startCompile,
|
||||
stopCompile,
|
||||
stopOnValidationError,
|
||||
switchLayout,
|
||||
|
@ -554,12 +422,9 @@ export default function PdfPreviewProvider({ children }) {
|
|||
}
|
||||
}, [
|
||||
autoCompile,
|
||||
autoCompileLintingError,
|
||||
codeCheckFailed,
|
||||
clearCache,
|
||||
clearingCache,
|
||||
clsiServerId,
|
||||
compileGroup,
|
||||
compiledOnce,
|
||||
compiling,
|
||||
draft,
|
||||
error,
|
||||
|
@ -571,22 +436,13 @@ export default function PdfPreviewProvider({ children }) {
|
|||
pdfLayout,
|
||||
pdfUrl,
|
||||
rawLog,
|
||||
recompile,
|
||||
recompileFromScratch,
|
||||
setAutoCompile,
|
||||
setClsiServerId,
|
||||
setCompileGroup,
|
||||
setCompiledOnce,
|
||||
setDraft,
|
||||
setError,
|
||||
setHasLintingError,
|
||||
setLogEntries,
|
||||
setPdfDownloadUrl,
|
||||
setPdfLayout,
|
||||
setPdfUrl,
|
||||
setHasLintingError, // only for stories
|
||||
setStopOnValidationError,
|
||||
setUiView,
|
||||
showLogs,
|
||||
startCompile,
|
||||
stopCompile,
|
||||
stopOnValidationError,
|
||||
switchLayout,
|
||||
|
@ -604,12 +460,9 @@ export default function PdfPreviewProvider({ children }) {
|
|||
PdfPreviewContext.Provider.propTypes = {
|
||||
value: PropTypes.shape({
|
||||
autoCompile: PropTypes.bool.isRequired,
|
||||
autoCompileLintingError: PropTypes.bool.isRequired,
|
||||
clearCache: PropTypes.func.isRequired,
|
||||
clearingCache: PropTypes.bool.isRequired,
|
||||
clsiServerId: PropTypes.string,
|
||||
compileGroup: PropTypes.string,
|
||||
compiledOnce: PropTypes.bool.isRequired,
|
||||
codeCheckFailed: PropTypes.bool.isRequired,
|
||||
compiling: PropTypes.bool.isRequired,
|
||||
draft: PropTypes.bool.isRequired,
|
||||
error: PropTypes.string,
|
||||
|
@ -621,23 +474,14 @@ PdfPreviewContext.Provider.propTypes = {
|
|||
pdfLayout: PropTypes.string,
|
||||
pdfUrl: PropTypes.string,
|
||||
rawLog: PropTypes.string,
|
||||
recompile: PropTypes.func.isRequired,
|
||||
recompileFromScratch: PropTypes.func.isRequired,
|
||||
setAutoCompile: PropTypes.func.isRequired,
|
||||
setClsiServerId: PropTypes.func.isRequired,
|
||||
setCompileGroup: PropTypes.func.isRequired,
|
||||
setCompiledOnce: PropTypes.func.isRequired,
|
||||
setDraft: PropTypes.func.isRequired,
|
||||
setError: PropTypes.func.isRequired,
|
||||
setHasLintingError: PropTypes.func.isRequired, // only for storybook
|
||||
setLogEntries: PropTypes.func.isRequired,
|
||||
setPdfDownloadUrl: PropTypes.func.isRequired,
|
||||
setPdfLayout: PropTypes.func.isRequired,
|
||||
setPdfUrl: PropTypes.func.isRequired,
|
||||
setShowLogs: PropTypes.func.isRequired,
|
||||
setStopOnValidationError: PropTypes.func.isRequired,
|
||||
setUiView: PropTypes.func.isRequired,
|
||||
showLogs: PropTypes.bool.isRequired,
|
||||
startCompile: PropTypes.func.isRequired,
|
||||
stopCompile: PropTypes.func.isRequired,
|
||||
stopOnValidationError: PropTypes.bool.isRequired,
|
||||
switchLayout: PropTypes.func.isRequired,
|
||||
|
|
179
services/web/frontend/js/features/pdf-preview/util/compiler.js
Normal file
179
services/web/frontend/js/features/pdf-preview/util/compiler.js
Normal file
|
@ -0,0 +1,179 @@
|
|||
import { isMainFile } from './editor-files'
|
||||
import getMeta from '../../../utils/meta'
|
||||
import { sendMBSampled } from '../../../infrastructure/event-tracking'
|
||||
import { deleteJSON, postJSON } from '../../../infrastructure/fetch-json'
|
||||
import { debounce } from 'lodash'
|
||||
|
||||
const AUTO_COMPILE_MAX_WAIT = 5000
|
||||
// We add a 1 second debounce to sending user changes to server if they aren't
|
||||
// collaborating with anyone. This needs to be higher than that, and allow for
|
||||
// client to server latency, otherwise we compile before the op reaches the server
|
||||
// and then again on ack.
|
||||
const AUTO_COMPILE_DEBOUNCE = 2000
|
||||
|
||||
const searchParams = new URLSearchParams(window.location.search)
|
||||
|
||||
export default class DocumentCompiler {
|
||||
constructor({
|
||||
project,
|
||||
setChangedAt,
|
||||
setCompiling,
|
||||
setData,
|
||||
setError,
|
||||
signal,
|
||||
}) {
|
||||
this.project = project
|
||||
this.setChangedAt = setChangedAt
|
||||
this.setCompiling = setCompiling
|
||||
this.setData = setData
|
||||
this.setError = setError
|
||||
this.signal = signal
|
||||
|
||||
this.clsiServerId = null
|
||||
this.currentDoc = null
|
||||
this.error = undefined
|
||||
this.timer = 0
|
||||
|
||||
this.debouncedAutoCompile = debounce(
|
||||
() => {
|
||||
this.compile({ isAutoCompileOnChange: true })
|
||||
},
|
||||
AUTO_COMPILE_DEBOUNCE,
|
||||
{
|
||||
maxWait: AUTO_COMPILE_MAX_WAIT,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.signal.abort()
|
||||
this.debouncedAutoCompile.cancel()
|
||||
}
|
||||
|
||||
// The main "compile" function.
|
||||
// Call this directly to run a compile now, otherwise call debouncedAutoCompile.
|
||||
async compile(options = {}) {
|
||||
// set "compiling" to true (in the React component's state), and return if it was already true
|
||||
let wasCompiling
|
||||
|
||||
this.setCompiling(oldValue => {
|
||||
wasCompiling = oldValue
|
||||
return true
|
||||
})
|
||||
|
||||
if (wasCompiling) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// log a sample of the compile requests
|
||||
sendMBSampled('editor-recompile-sampled', options)
|
||||
|
||||
// reset values
|
||||
this.setChangedAt(0)
|
||||
this.validationIssues = undefined
|
||||
|
||||
window.dispatchEvent(new CustomEvent('flush-changes')) // TODO: wait for this?
|
||||
|
||||
const params = this.buildQueryParams()
|
||||
this.addCompileParams(params, options)
|
||||
|
||||
const data = await postJSON(
|
||||
`/project/${this.project._id}/compile?${params}`,
|
||||
{
|
||||
body: {
|
||||
rootDoc_id: this.getRootDocOverrideId(),
|
||||
draft: this.draft,
|
||||
check: 'silent', // NOTE: 'error' and 'validate' are possible, but unused
|
||||
// use incremental compile for all users but revert to a full compile
|
||||
// if there was previously a server error
|
||||
incrementalCompilesEnabled: !this.error,
|
||||
},
|
||||
signal: this.signal,
|
||||
}
|
||||
)
|
||||
data.options = options
|
||||
this.setData(data)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
this.setError(error.info?.statusCode === 429 ? 'rate-limited' : 'error')
|
||||
} finally {
|
||||
this.setCompiling(false)
|
||||
}
|
||||
}
|
||||
|
||||
// parse the text of the current doc in the editor
|
||||
// if it contains "\documentclass" then use this as the root doc
|
||||
getRootDocOverrideId() {
|
||||
// only override when not in the root doc itself
|
||||
if (this.currentDoc.doc_id !== this.project.rootDoc_id) {
|
||||
const snapshot = this.currentDoc.getSnapshot()
|
||||
|
||||
if (snapshot && isMainFile(snapshot)) {
|
||||
return this.currentDoc.doc_id
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
// build the query parameters added to all requests
|
||||
buildQueryParams() {
|
||||
const params = new URLSearchParams()
|
||||
|
||||
// the id of the CLSI server that processed the previous compile request
|
||||
if (this.clsiServerId) {
|
||||
params.set('clsiserverid', this.clsiServerId)
|
||||
}
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
// add extra query parameters to the compile request
|
||||
addCompileParams(params, options) {
|
||||
// tell the server whether this is an automatic or manual compile request
|
||||
if (options.isAutoCompileOnLoad || options.isAutoCompileOnChange) {
|
||||
params.set('auto_compile', 'true')
|
||||
}
|
||||
|
||||
// use the feature flag to enable PDF caching in a ServiceWorker
|
||||
if (getMeta('ol-enablePdfCaching')) {
|
||||
params.set('enable_pdf_caching', 'true')
|
||||
}
|
||||
|
||||
// use the feature flag to enable "file line errors"
|
||||
if (searchParams.get('file_line_errors') === 'true') {
|
||||
params.file_line_errors = 'true'
|
||||
}
|
||||
}
|
||||
|
||||
// send a request to stop the current compile
|
||||
stopCompile() {
|
||||
// NOTE: no stoppingCompile state, as this should happen fairly quickly
|
||||
// and doesn't matter if it runs twice.
|
||||
|
||||
const params = this.buildQueryParams()
|
||||
|
||||
return postJSON(`/project/${this.project._id}/compile/stop?${params}`, {
|
||||
signal: this.signal,
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error)
|
||||
this.setError('error')
|
||||
})
|
||||
.finally(() => {
|
||||
this.setCompiling(false)
|
||||
})
|
||||
}
|
||||
|
||||
clearCache() {
|
||||
const params = this.buildQueryParams()
|
||||
|
||||
return deleteJSON(`/project/${this.project._id}/output?${params}`, {
|
||||
signal: this.signal,
|
||||
}).catch(error => {
|
||||
console.error(error)
|
||||
this.setError('clear-cache')
|
||||
})
|
||||
}
|
||||
}
|
|
@ -109,6 +109,6 @@ export const Interactive = () => {
|
|||
<PdfJsViewer url="/build/output.pdf" />
|
||||
<Inner />
|
||||
</div>,
|
||||
{ scope: { project } }
|
||||
{ project }
|
||||
)
|
||||
}
|
||||
|
|
|
@ -47,6 +47,12 @@ const scope = {
|
|||
},
|
||||
hasLintingError: false,
|
||||
$applyAsync: () => {},
|
||||
editor: {
|
||||
sharejs_doc: {
|
||||
doc_id: 'test-doc',
|
||||
getSnapshot: () => 'some doc content',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const dispatchProjectJoined = () => {
|
||||
|
|
|
@ -9,7 +9,7 @@ const outputFiles = [
|
|||
{
|
||||
path: 'output.pdf',
|
||||
build: '123',
|
||||
// url: 'about:blank', // TODO: PDF URL to render
|
||||
url: '/build/output.pdf', // TODO: PDF URL to render
|
||||
type: 'pdf',
|
||||
},
|
||||
{
|
||||
|
@ -119,6 +119,18 @@ const storeAndFireEvent = (key, value) => {
|
|||
fireEvent(window, new StorageEvent('storage', { key }))
|
||||
}
|
||||
|
||||
const scope = {
|
||||
settings: {
|
||||
syntaxValidation: false,
|
||||
},
|
||||
editor: {
|
||||
sharejs_doc: {
|
||||
doc_id: 'test-doc',
|
||||
getSnapshot: () => 'some doc content',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
describe('<PdfPreview/>', function () {
|
||||
var clock
|
||||
|
||||
|
@ -138,50 +150,31 @@ describe('<PdfPreview/>', function () {
|
|||
localStorage.clear()
|
||||
})
|
||||
|
||||
it('renders the PDF preview', function () {
|
||||
renderWithEditorContext(<PdfPreview />)
|
||||
|
||||
screen.getByRole('button', { name: 'Recompile' })
|
||||
})
|
||||
|
||||
it('runs a compile only on the first project:joined event', async function () {
|
||||
it('renders the PDF preview', async function () {
|
||||
mockCompile()
|
||||
mockBuildFile()
|
||||
|
||||
renderWithEditorContext(<PdfPreview />)
|
||||
renderWithEditorContext(<PdfPreview />, { scope })
|
||||
|
||||
// fire a project:joined event => compile
|
||||
screen.getByRole('button', { name: 'Recompile' })
|
||||
fireEvent(window, new CustomEvent('project:joined'))
|
||||
screen.getByRole('button', { name: 'Compiling…' })
|
||||
// wait for "compile on load" to finish
|
||||
await screen.findByRole('button', { name: 'Compiling…' })
|
||||
await screen.findByRole('button', { name: 'Recompile' })
|
||||
|
||||
// fire another project:joined event => no compile
|
||||
fireEvent(window, new CustomEvent('project:joined'))
|
||||
screen.getByRole('button', { name: 'Recompile' })
|
||||
|
||||
expect(fetchMock.calls()).to.have.length(3)
|
||||
|
||||
expect(fetchMock.called('express:/project/:projectId/compile')).to.be.true // TODO: auto_compile query param
|
||||
expect(fetchMock.called('express:/build/:file')).to.be.true // TODO: actual path
|
||||
})
|
||||
|
||||
it('runs a compile when the Recompile button is pressed', async function () {
|
||||
mockCompile()
|
||||
mockBuildFile()
|
||||
|
||||
renderWithEditorContext(<PdfPreview />)
|
||||
renderWithEditorContext(<PdfPreview />, { scope })
|
||||
|
||||
// fire a project:joined event => compile
|
||||
screen.getByRole('button', { name: 'Recompile' })
|
||||
fireEvent(window, new CustomEvent('project:joined'))
|
||||
screen.getByRole('button', { name: 'Compiling…' })
|
||||
// wait for "compile on load" to finish
|
||||
await screen.findByRole('button', { name: 'Compiling…' })
|
||||
await screen.findByRole('button', { name: 'Recompile' })
|
||||
|
||||
// press the Recompile button => compile
|
||||
const button = screen.getByRole('button', { name: 'Recompile' })
|
||||
button.click()
|
||||
screen.getByRole('button', { name: 'Compiling…' })
|
||||
await screen.findByRole('button', { name: 'Compiling…' })
|
||||
await screen.findByRole('button', { name: 'Recompile' })
|
||||
|
||||
expect(fetchMock.calls()).to.have.length(6)
|
||||
|
@ -191,12 +184,10 @@ describe('<PdfPreview/>', function () {
|
|||
mockCompile()
|
||||
mockBuildFile()
|
||||
|
||||
renderWithEditorContext(<PdfPreview />)
|
||||
renderWithEditorContext(<PdfPreview />, { scope })
|
||||
|
||||
// fire a project:joined event => compile
|
||||
screen.getByRole('button', { name: 'Recompile' })
|
||||
fireEvent(window, new CustomEvent('project:joined'))
|
||||
screen.getByRole('button', { name: 'Compiling…' })
|
||||
// wait for "compile on load" to finish
|
||||
await screen.findByRole('button', { name: 'Compiling…' })
|
||||
await screen.findByRole('button', { name: 'Recompile' })
|
||||
|
||||
// switch on auto compile
|
||||
|
@ -206,7 +197,7 @@ describe('<PdfPreview/>', function () {
|
|||
fireEvent(window, new CustomEvent('doc:changed'))
|
||||
clock.tick(2000) // AUTO_COMPILE_DEBOUNCE
|
||||
|
||||
await screen.findByText('Compiling…')
|
||||
await screen.findByRole('button', { name: 'Compiling…' })
|
||||
await screen.findByRole('button', { name: 'Recompile' })
|
||||
|
||||
expect(fetchMock.calls()).to.have.length(6)
|
||||
|
@ -216,17 +207,14 @@ describe('<PdfPreview/>', function () {
|
|||
mockCompile()
|
||||
mockBuildFile()
|
||||
|
||||
renderWithEditorContext(<PdfPreview />)
|
||||
renderWithEditorContext(<PdfPreview />, { scope })
|
||||
|
||||
// fire a project:joined event => compile
|
||||
screen.getByRole('button', { name: 'Recompile' })
|
||||
fireEvent(window, new CustomEvent('project:joined'))
|
||||
screen.getByRole('button', { name: 'Compiling…' })
|
||||
// wait for "compile on load" to finish
|
||||
await screen.findByRole('button', { name: 'Compiling…' })
|
||||
await screen.findByRole('button', { name: 'Recompile' })
|
||||
|
||||
// make sure auto compile is switched off
|
||||
storeAndFireEvent('autocompile_enabled:project123', false)
|
||||
screen.getByRole('button', { name: 'Recompile' })
|
||||
|
||||
// fire a doc:changed event => no compile
|
||||
fireEvent(window, new CustomEvent('doc:changed'))
|
||||
|
@ -242,21 +230,19 @@ describe('<PdfPreview/>', function () {
|
|||
|
||||
renderWithEditorContext(<PdfPreview />, {
|
||||
scope: {
|
||||
...scope,
|
||||
'settings.syntaxValidation': true, // enable linting in the editor
|
||||
hasLintingError: true, // mock a linting error
|
||||
},
|
||||
})
|
||||
|
||||
// fire a project:joined event => compile
|
||||
screen.getByRole('button', { name: 'Recompile' })
|
||||
fireEvent(window, new CustomEvent('project:joined'))
|
||||
screen.getByRole('button', { name: 'Compiling…' })
|
||||
// wait for "compile on load" to finish
|
||||
await screen.findByRole('button', { name: 'Compiling…' })
|
||||
await screen.findByRole('button', { name: 'Recompile' })
|
||||
|
||||
// switch on auto compile and syntax checking
|
||||
storeAndFireEvent('autocompile_enabled:project123', true)
|
||||
storeAndFireEvent('stop_on_validation_error:project123', true)
|
||||
screen.getByRole('button', { name: 'Recompile' })
|
||||
|
||||
// fire a doc:changed event => no compile
|
||||
fireEvent(window, new CustomEvent('doc:changed'))
|
||||
|
@ -270,13 +256,12 @@ describe('<PdfPreview/>', function () {
|
|||
it('displays an error message if there was a compile error', async function () {
|
||||
mockCompileError('compile-in-progress')
|
||||
|
||||
renderWithEditorContext(<PdfPreview />)
|
||||
renderWithEditorContext(<PdfPreview />, { scope })
|
||||
|
||||
// fire a project:joined event => compile
|
||||
screen.getByRole('button', { name: 'Recompile' })
|
||||
fireEvent(window, new CustomEvent('project:joined'))
|
||||
screen.getByRole('button', { name: 'Compiling…' })
|
||||
// wait for "compile on load" to finish
|
||||
await screen.findByRole('button', { name: 'Compiling…' })
|
||||
await screen.findByRole('button', { name: 'Recompile' })
|
||||
|
||||
screen.getByText(
|
||||
'Please wait for your other compile to finish before trying again.'
|
||||
)
|
||||
|
@ -285,27 +270,6 @@ describe('<PdfPreview/>', function () {
|
|||
expect(fetchMock.called('express:/build/:file')).to.be.false // TODO: actual path
|
||||
})
|
||||
|
||||
it('disables the view logs button if there is a compile error', async function () {
|
||||
mockCompileError()
|
||||
mockBuildFile()
|
||||
|
||||
renderWithEditorContext(<PdfPreview />)
|
||||
|
||||
const logsButton = screen.getByRole('button', { name: 'View logs' })
|
||||
expect(logsButton.hasAttribute('disabled')).to.be.false
|
||||
|
||||
// fire a project:joined event => compile
|
||||
screen.getByRole('button', { name: 'Recompile' })
|
||||
fireEvent(window, new CustomEvent('project:joined'))
|
||||
screen.getByRole('button', { name: 'Compiling…' })
|
||||
expect(logsButton.hasAttribute('disabled')).to.be.false
|
||||
await screen.findByRole('button', { name: 'Recompile' })
|
||||
expect(logsButton.hasAttribute('disabled')).to.be.true
|
||||
|
||||
expect(fetchMock.called('express:/project/:projectId/compile')).to.be.true // TODO: auto_compile query param
|
||||
expect(fetchMock.called('express:/build/:file')).to.be.false // TODO: actual path
|
||||
})
|
||||
|
||||
it('displays error messages if there were validation problems', async function () {
|
||||
const validationProblems = {
|
||||
sizeCheck: {
|
||||
|
@ -327,13 +291,12 @@ describe('<PdfPreview/>', function () {
|
|||
|
||||
mockValidationProblems(validationProblems)
|
||||
|
||||
renderWithEditorContext(<PdfPreview />)
|
||||
renderWithEditorContext(<PdfPreview />, { scope })
|
||||
|
||||
// fire a project:joined event => compile
|
||||
screen.getByRole('button', { name: 'Recompile' })
|
||||
fireEvent(window, new CustomEvent('project:joined'))
|
||||
screen.getByRole('button', { name: 'Compiling…' })
|
||||
// wait for "compile on load" to finish
|
||||
await screen.findByRole('button', { name: 'Compiling…' })
|
||||
await screen.findByRole('button', { name: 'Recompile' })
|
||||
|
||||
screen.getByText('Project too large')
|
||||
screen.getByText('Unknown main document')
|
||||
screen.getByText('Conflicting Paths Found')
|
||||
|
@ -347,24 +310,18 @@ describe('<PdfPreview/>', function () {
|
|||
mockBuildFile()
|
||||
mockClearCache()
|
||||
|
||||
renderWithEditorContext(<PdfPreview />)
|
||||
renderWithEditorContext(<PdfPreview />, { scope })
|
||||
|
||||
const logsButton = screen.getByRole('button', { name: 'View logs' })
|
||||
logsButton.click()
|
||||
|
||||
let clearCacheButton = screen.getByRole('button', {
|
||||
name: 'Clear cached files',
|
||||
})
|
||||
expect(clearCacheButton.hasAttribute('disabled')).to.be.false
|
||||
|
||||
// fire a project:joined event => compile
|
||||
screen.getByRole('button', { name: 'Recompile' })
|
||||
fireEvent(window, new CustomEvent('project:joined'))
|
||||
screen.getByRole('button', { name: 'Compiling…' })
|
||||
expect(clearCacheButton.hasAttribute('disabled')).to.be.true
|
||||
// wait for "compile on load" to finish
|
||||
await screen.findByRole('button', { name: 'Compiling…' })
|
||||
await screen.findByRole('button', { name: 'Recompile' })
|
||||
|
||||
const logsButton = screen.getByRole('button', {
|
||||
name: 'This project has an error',
|
||||
})
|
||||
logsButton.click()
|
||||
clearCacheButton = await screen.findByRole('button', {
|
||||
|
||||
const clearCacheButton = await screen.findByRole('button', {
|
||||
name: 'Clear cached files',
|
||||
})
|
||||
expect(clearCacheButton.hasAttribute('disabled')).to.be.false
|
||||
|
@ -379,4 +336,43 @@ describe('<PdfPreview/>', function () {
|
|||
expect(fetchMock.called('express:/project/:projectId/compile')).to.be.true // TODO: auto_compile query param
|
||||
expect(fetchMock.called('express:/build/:file')).to.be.true // TODO: actual path
|
||||
})
|
||||
|
||||
it('handle "recompile from scratch"', async function () {
|
||||
mockCompile()
|
||||
mockBuildFile()
|
||||
mockClearCache()
|
||||
|
||||
renderWithEditorContext(<PdfPreview />, { scope })
|
||||
|
||||
// wait for "compile on load" to finish
|
||||
await screen.findByRole('button', { name: 'Compiling…' })
|
||||
await screen.findByRole('button', { name: 'Recompile' })
|
||||
|
||||
// show the logs UI
|
||||
const logsButton = screen.getByRole('button', {
|
||||
name: 'This project has an error',
|
||||
})
|
||||
logsButton.click()
|
||||
|
||||
const clearCacheButton = await screen.findByRole('button', {
|
||||
name: 'Clear cached files',
|
||||
})
|
||||
expect(clearCacheButton.hasAttribute('disabled')).to.be.false
|
||||
|
||||
const recompileFromScratch = screen.getByRole('menuitem', {
|
||||
name: 'Recompile from scratch',
|
||||
hidden: true,
|
||||
})
|
||||
recompileFromScratch.click()
|
||||
|
||||
expect(clearCacheButton.hasAttribute('disabled')).to.be.true
|
||||
|
||||
// wait for compile to finish
|
||||
await screen.findByRole('button', { name: 'Compiling…' })
|
||||
await screen.findByRole('button', { name: 'Recompile' })
|
||||
|
||||
expect(fetchMock.called('express:/project/:projectId/compile')).to.be.true // TODO: auto_compile query param
|
||||
expect(fetchMock.called('express:/project/:projectId/output')).to.be.true
|
||||
expect(fetchMock.called('express:/build/:file')).to.be.true // TODO: actual path
|
||||
})
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue