pdf-preview.spec.tsx 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872
  1. import localStorage from '@/infrastructure/local-storage'
  2. import PdfPreview from '../../../../frontend/js/features/pdf-preview/components/pdf-preview'
  3. import { EditorProviders } from '../../helpers/editor-providers'
  4. import { mockScope } from './scope'
  5. import {
  6. IdeLayout,
  7. IdeView,
  8. useLayoutContext,
  9. } from '../../../../frontend/js/shared/context/layout-context'
  10. import { FC, useEffect } from 'react'
  11. const storeAndFireEvent = (win: typeof window, key: string, value: unknown) => {
  12. localStorage.setItem(key, value)
  13. win.dispatchEvent(new StorageEvent('storage', { key }))
  14. }
  15. const Layout: FC<{ layout: IdeLayout; view?: IdeView }> = ({
  16. layout,
  17. view,
  18. }) => {
  19. const { changeLayout } = useLayoutContext()
  20. useEffect(() => {
  21. changeLayout(layout, view)
  22. }, [changeLayout, layout, view])
  23. return null
  24. }
  25. describe('<PdfPreview/>', function () {
  26. let projectId: string
  27. beforeEach(function () {
  28. /**
  29. * There are time sensitive tests in this test suite. They need to wait for a Promise before resolving a request.
  30. *
  31. * Using a promise across the test-env (browser) vs stub-env (server) causes additional latency.
  32. *
  33. * This latency seems to stack up when adding more intercepts for the same path. Using static responses for some of these intercepts does not help.
  34. *
  35. * All of that seems like a bug in Cypress. For now just work around it by using a unique projectId for each intercept.
  36. */
  37. projectId = Math.random().toString().slice(2)
  38. window.metaAttributesCache.set('ol-preventCompileOnLoad', true)
  39. window.metaAttributesCache.set(
  40. 'ol-compilesUserContentDomain',
  41. 'https://compiles-user.dev-overleaf.com'
  42. )
  43. window.metaAttributesCache.set('ol-splitTestVariants', {
  44. 'initial-compile-from-clsi-cache': 'enabled',
  45. })
  46. cy.interceptEvents()
  47. })
  48. it('renders the PDF preview', function () {
  49. window.metaAttributesCache.set('ol-preventCompileOnLoad', false)
  50. cy.interceptCompile()
  51. const scope = mockScope()
  52. cy.mount(
  53. <EditorProviders scope={scope}>
  54. <div className="pdf-viewer">
  55. <PdfPreview />
  56. </div>
  57. </EditorProviders>
  58. )
  59. // wait for "compile on load" to finish
  60. cy.waitForCompile({ pdf: true })
  61. cy.findByRole('button', { name: 'Recompile' })
  62. })
  63. it('uses the cache when available', function () {
  64. cy.interceptCompile({
  65. prefix: 'compile',
  66. times: 1,
  67. cached: true,
  68. regular: false,
  69. })
  70. const scope = mockScope()
  71. cy.mount(
  72. <EditorProviders scope={scope}>
  73. <div className="pdf-viewer">
  74. <PdfPreview />
  75. </div>
  76. </EditorProviders>
  77. )
  78. // wait for "compile from cache on load" to finish
  79. cy.waitForCompile({ pdf: true, cached: true, regular: false })
  80. cy.contains('Your Paper')
  81. })
  82. it('uses the cache when available then compiles', function () {
  83. cy.interceptCompile({
  84. prefix: 'compile',
  85. times: 1,
  86. cached: true,
  87. regular: false,
  88. })
  89. const scope = mockScope()
  90. cy.mount(
  91. <EditorProviders scope={scope}>
  92. <div className="pdf-viewer">
  93. <PdfPreview />
  94. </div>
  95. </EditorProviders>
  96. )
  97. // wait for "compile from cache on load" to finish
  98. cy.waitForCompile({ pdf: true, cached: true, regular: false })
  99. cy.contains('Your Paper')
  100. // Then trigger a new compile
  101. cy.interceptCompile({
  102. prefix: 'recompile',
  103. times: 1,
  104. cached: false,
  105. outputPDFFixture: 'output-2.pdf',
  106. })
  107. // press the Recompile button => compile
  108. cy.findByRole('button', { name: 'Recompile' }).click()
  109. // wait for compile to finish
  110. cy.waitForCompile({ prefix: 'recompile', pdf: true })
  111. cy.contains('Modern Authoring Tools for Science')
  112. })
  113. describe('racing compile from cache and regular compile trigger', function () {
  114. for (const [timing] of ['before rendering', 'after rendering']) {
  115. it(`replaces the compile from cache with a regular compile - ${timing}`, function () {
  116. const requestedOnce = new Set()
  117. ;['log', 'pdf', 'blg'].forEach(ext => {
  118. cy.intercept({ pathname: `/build/*/output.${ext}` }, req => {
  119. if (requestedOnce.has(ext)) {
  120. throw new Error(
  121. `compile from cache triggered extra ${ext} request: ${req.url}`
  122. )
  123. }
  124. requestedOnce.add(ext)
  125. req.reply({ fixture: `build/output.${ext},null` })
  126. }).as(`compile-${ext}`)
  127. })
  128. const { promise, resolve } = Promise.withResolvers<void>()
  129. cy.interceptCompileFromCacheRequest({
  130. promise,
  131. times: 1,
  132. }).as('cached-compile')
  133. cy.interceptCompileRequest().as('compile')
  134. const scope = mockScope()
  135. cy.mount(
  136. <EditorProviders scope={scope} projectId={projectId}>
  137. <div className="pdf-viewer">
  138. <PdfPreview />
  139. </div>
  140. </EditorProviders>
  141. )
  142. // press the Recompile button => compile
  143. cy.findByRole('button', { name: 'Recompile' }).click()
  144. if (timing === 'before rendering') {
  145. cy.then(() => resolve())
  146. cy.wait('@cached-compile')
  147. }
  148. // wait for rendering to finish
  149. cy.waitForCompile({ pdf: true, cached: false })
  150. if (timing === 'after rendering') {
  151. cy.then(() => resolve())
  152. cy.wait('@cached-compile')
  153. }
  154. cy.contains('Your Paper')
  155. cy.then(() => Array.from(requestedOnce).sort().join(',')).should(
  156. 'equal',
  157. 'blg,log,pdf'
  158. )
  159. })
  160. }
  161. })
  162. describe('clsi-cache project settings validation', function () {
  163. const cases = {
  164. // Flaky, skip for now
  165. 'uses compile from cache when nothing changed': {
  166. cached: true,
  167. setup: () => {},
  168. props: {},
  169. },
  170. 'ignores the compile from cache when imageName changed': {
  171. cached: false,
  172. setup: () => {},
  173. props: {
  174. imageName: 'texlive-full:2025.1',
  175. },
  176. },
  177. 'ignores the compile from cache when compiler changed': {
  178. cached: false,
  179. setup: () => {},
  180. props: {
  181. compiler: 'lualatex',
  182. },
  183. },
  184. 'ignores the compile from cache when draft mode changed': {
  185. cached: false,
  186. setup: () => {
  187. cy.window().then(w =>
  188. w.localStorage.setItem(`draft:${projectId}`, 'true')
  189. )
  190. },
  191. props: {},
  192. },
  193. 'ignores the compile from cache when stopOnFirstError mode changed': {
  194. cached: false,
  195. setup: () => {
  196. cy.window().then(w =>
  197. w.localStorage.setItem(`stop_on_first_error:${projectId}`, 'true')
  198. )
  199. },
  200. props: {},
  201. },
  202. 'ignores the compile from cache when rootDoc changed': {
  203. cached: false,
  204. setup: () => {},
  205. props: {
  206. rootDocId: 'new-root-doc-id',
  207. rootFolder: [
  208. {
  209. _id: 'root-folder-id',
  210. name: 'rootFolder',
  211. docs: [
  212. {
  213. _id: '_root_doc_id',
  214. name: 'main.tex',
  215. },
  216. {
  217. _id: 'new-root-doc-id',
  218. name: 'new-main.tex',
  219. },
  220. ],
  221. folders: [],
  222. fileRefs: [],
  223. },
  224. ],
  225. },
  226. },
  227. }
  228. Object.entries(cases).forEach(([name, { cached, setup, props }]) => {
  229. it(name, function () {
  230. cy.interceptCompile({
  231. cached: true,
  232. regular: !cached,
  233. })
  234. const scope = mockScope()
  235. window.metaAttributesCache.set('ol-preventCompileOnLoad', false)
  236. setup()
  237. cy.mount(
  238. <EditorProviders scope={scope} projectId={projectId} {...props}>
  239. <div className="pdf-viewer">
  240. <PdfPreview />
  241. </div>
  242. </EditorProviders>
  243. )
  244. // wait for compile to finish
  245. cy.waitForCompile({ pdf: true, cached, regular: !cached })
  246. cy.contains('Your Paper')
  247. })
  248. })
  249. })
  250. it('runs a compile when the Recompile button is pressed', function () {
  251. cy.interceptCompile()
  252. const scope = mockScope()
  253. cy.mount(
  254. <EditorProviders scope={scope}>
  255. <div className="pdf-viewer">
  256. <PdfPreview />
  257. </div>
  258. </EditorProviders>
  259. )
  260. // press the Recompile button => compile
  261. cy.findByRole('button', { name: 'Recompile' }).click()
  262. // wait for compile to finish
  263. cy.waitForCompile({ pdf: true })
  264. cy.contains('Your Paper')
  265. })
  266. it('runs a compile on `pdf:recompile` event', function () {
  267. cy.interceptCompile()
  268. const scope = mockScope()
  269. cy.mount(
  270. <EditorProviders scope={scope}>
  271. <div className="pdf-viewer">
  272. <PdfPreview />
  273. </div>
  274. </EditorProviders>
  275. )
  276. cy.window().then(win => {
  277. win.dispatchEvent(new CustomEvent('pdf:recompile'))
  278. })
  279. // wait for compile to finish
  280. cy.waitForCompile({ pdf: true })
  281. cy.contains('Your Paper')
  282. })
  283. it('does not compile while compiling', function () {
  284. let counter = 0
  285. cy.interceptDeferredCompile(() => counter++).then(
  286. resolveDeferredCompile => {
  287. const scope = mockScope()
  288. cy.mount(
  289. <EditorProviders scope={scope}>
  290. <div className="pdf-viewer">
  291. <PdfPreview />
  292. </div>
  293. </EditorProviders>
  294. )
  295. // start compiling
  296. cy.findByRole('button', { name: 'Recompile' }).click()
  297. cy.findByRole('button', { name: 'Compiling…' }).then(() => {
  298. // trigger a recompile
  299. cy.window().then(win => {
  300. win.dispatchEvent(new CustomEvent('pdf:recompile'))
  301. })
  302. // finish the original compile
  303. resolveDeferredCompile()
  304. // wait for the original compile to finish
  305. cy.waitForCompile().then(() => {
  306. // NOTE: difficult to assert that a second request won't be sent, at some point
  307. expect(counter).to.equal(1)
  308. })
  309. })
  310. }
  311. )
  312. })
  313. it('disables compile button while compile is running', function () {
  314. cy.interceptDeferredCompile().then(resolveDeferredCompile => {
  315. const scope = mockScope()
  316. cy.mount(
  317. <EditorProviders scope={scope}>
  318. <div className="pdf-viewer">
  319. <PdfPreview />
  320. </div>
  321. </EditorProviders>
  322. )
  323. cy.findByRole('button', { name: 'Recompile' }).click()
  324. cy.findByRole('button', { name: 'Compiling…' })
  325. .should('be.disabled')
  326. .then(() => resolveDeferredCompile())
  327. cy.waitForCompile()
  328. cy.findByRole('button', { name: 'Recompile' }).should('not.be.disabled')
  329. })
  330. })
  331. it('runs a compile on doc change if autocompile is enabled', function () {
  332. cy.interceptCompile()
  333. const scope = mockScope()
  334. cy.mount(
  335. <EditorProviders scope={scope}>
  336. <div className="pdf-viewer">
  337. <PdfPreview />
  338. </div>
  339. </EditorProviders>
  340. )
  341. cy.window().then(win => {
  342. cy.clock()
  343. // switch on auto compile
  344. storeAndFireEvent(win, 'autocompile_enabled:project123', true)
  345. // fire a doc:changed event => compile
  346. win.dispatchEvent(new CustomEvent('doc:changed'))
  347. // wait enough time for the compile to start
  348. cy.tick(6000) // > AUTO_COMPILE_DEBOUNCE
  349. cy.clock().invoke('restore')
  350. })
  351. // wait for compile to finish
  352. cy.waitForCompile({ pdf: true })
  353. cy.findByRole('button', { name: 'Recompile' })
  354. })
  355. it('does not run a compile on doc change if autocompile is disabled', function () {
  356. cy.interceptCompile()
  357. const scope = mockScope()
  358. cy.mount(
  359. <EditorProviders scope={scope}>
  360. <div className="pdf-viewer">
  361. <PdfPreview />
  362. </div>
  363. </EditorProviders>
  364. )
  365. cy.window().then(win => {
  366. cy.clock()
  367. // make sure auto compile is switched off
  368. storeAndFireEvent(win, 'autocompile_enabled:project123', false)
  369. // fire a doc:changed event => no compile
  370. win.dispatchEvent(new CustomEvent('doc:changed'))
  371. // wait enough time for the compile to start
  372. cy.tick(6000) // AUTO_COMPILE_DEBOUNCE
  373. cy.clock().invoke('restore')
  374. })
  375. // NOTE: difficult to assert that a request hasn't been sent
  376. cy.findByRole('button', { name: 'Recompile' })
  377. })
  378. it('does not run a compile on doc change if autocompile is blocked by syntax check', function () {
  379. cy.interceptCompile()
  380. const scope = mockScope()
  381. // enable linting in the editor
  382. const userSettings = { syntaxValidation: true }
  383. // mock a linting error
  384. scope.hasLintingError = true
  385. cy.mount(
  386. <EditorProviders scope={scope} userSettings={userSettings}>
  387. <div className="pdf-viewer">
  388. <PdfPreview />
  389. </div>
  390. </EditorProviders>
  391. )
  392. cy.window().then(win => {
  393. cy.clock()
  394. // switch on auto compile
  395. storeAndFireEvent(win, 'autocompile_enabled:project123', true)
  396. // switch on syntax checking
  397. storeAndFireEvent(win, 'stop_on_validation_error', true)
  398. // fire a doc:changed event => no compile
  399. win.dispatchEvent(new CustomEvent('doc:changed'))
  400. // wait enough time for the compile to start
  401. cy.tick(6000) // AUTO_COMPILE_DEBOUNCE
  402. cy.clock().invoke('restore')
  403. })
  404. // NOTE: difficult to assert that a request hasn't been sent
  405. cy.findByRole('button', { name: 'Recompile' })
  406. cy.findByText('Code check failed')
  407. })
  408. it('does not run a compile on doc change if the PDF preview is not open', function () {
  409. cy.interceptCompile()
  410. const scope = mockScope()
  411. cy.mount(
  412. <EditorProviders scope={scope}>
  413. <Layout layout="flat" view="editor" />
  414. <div className="pdf-viewer">
  415. <PdfPreview />
  416. </div>
  417. </EditorProviders>
  418. )
  419. cy.window().then(win => {
  420. cy.clock()
  421. // switch on auto compile
  422. storeAndFireEvent(win, 'autocompile_enabled:project123', true)
  423. // fire a doc:changed event => compile
  424. win.dispatchEvent(new CustomEvent('doc:changed'))
  425. // wait enough time for the compile to start
  426. cy.tick(6000) // > AUTO_COMPILE_DEBOUNCE
  427. cy.clock().invoke('restore')
  428. })
  429. // NOTE: difficult to assert that a request hasn't been sent
  430. cy.findByRole('button', { name: 'Recompile' })
  431. })
  432. describe('error messages', function () {
  433. const compileErrorStatuses = {
  434. 'clear-cache':
  435. 'Sorry, something went wrong and your project could not be compiled. Please try again in a few moments.',
  436. 'clsi-maintenance':
  437. 'The compile servers are down for maintenance, and will be back shortly.',
  438. 'compile-in-progress':
  439. 'A previous compile is still running. Please wait a minute and try compiling again.',
  440. exited: 'Server Error',
  441. failure: 'No PDF',
  442. generic: 'Server Error',
  443. 'project-too-large': 'Project too large',
  444. 'rate-limited': 'Compile rate limit hit',
  445. terminated: 'Compilation cancelled',
  446. timedout: 'Timed out',
  447. 'too-recently-compiled':
  448. 'This project was compiled very recently, so this compile has been skipped.',
  449. unavailable:
  450. 'Sorry, the compile server for your project was temporarily unavailable. Please try again in a few moments.',
  451. foo: 'Sorry, something went wrong and your project could not be compiled. Please try again in a few moments.',
  452. }
  453. for (const [status, message] of Object.entries(compileErrorStatuses)) {
  454. it(`displays error message for '${status}' status`, function () {
  455. cy.intercept('POST', '/project/*/compile*', {
  456. body: {
  457. status,
  458. clsiServerId: 'foo',
  459. compileGroup: 'priority',
  460. },
  461. }).as('compile')
  462. const scope = mockScope()
  463. cy.mount(
  464. <EditorProviders scope={scope}>
  465. <div className="pdf-viewer">
  466. <PdfPreview />
  467. </div>
  468. </EditorProviders>
  469. )
  470. cy.findByRole('button', { name: 'Recompile' }).click()
  471. cy.wait('@compile')
  472. cy.findByText(message)
  473. })
  474. }
  475. })
  476. it('displays expandable raw logs', function () {
  477. cy.interceptCompile()
  478. const scope = mockScope()
  479. cy.mount(
  480. <EditorProviders scope={scope}>
  481. <div className="pdf-viewer">
  482. <PdfPreview />
  483. </div>
  484. </EditorProviders>
  485. )
  486. cy.findByRole('button', { name: 'Recompile' }).click()
  487. cy.waitForCompile({ pdf: true })
  488. cy.findByRole('button', { name: 'View logs' }).click()
  489. cy.findByRole('button', { name: 'View PDF' })
  490. cy.findByRole('button', { name: 'Expand' }).click()
  491. cy.findByRole('button', { name: 'Collapse' }).click()
  492. })
  493. it('displays error messages if there were validation problems', function () {
  494. const validationProblems = {
  495. sizeCheck: {
  496. resources: [
  497. { path: 'foo/bar', kbSize: 76221 },
  498. { path: 'bar/baz', kbSize: 2342 },
  499. ],
  500. },
  501. mainFile: true,
  502. conflictedPaths: [
  503. {
  504. path: 'foo/bar',
  505. },
  506. {
  507. path: 'foo/baz',
  508. },
  509. ],
  510. }
  511. cy.intercept('POST', '/project/*/compile*', {
  512. body: {
  513. status: 'validation-problems',
  514. validationProblems,
  515. clsiServerId: 'foo',
  516. compileGroup: 'priority',
  517. },
  518. }).as('compile')
  519. const scope = mockScope()
  520. cy.mount(
  521. <EditorProviders scope={scope}>
  522. <div className="pdf-viewer">
  523. <PdfPreview />
  524. </div>
  525. </EditorProviders>
  526. )
  527. cy.findByRole('button', { name: 'Recompile' }).click()
  528. cy.wait('@compile')
  529. cy.findByText('Project too large')
  530. cy.findByText('Unknown main document')
  531. cy.findByText('Conflicting Paths Found')
  532. })
  533. describe('clear cache', function () {
  534. it('sends a clear cache request when the button is pressed', function () {
  535. cy.interceptCompile()
  536. const scope = mockScope()
  537. cy.mount(
  538. <EditorProviders scope={scope}>
  539. <div className="pdf-viewer">
  540. <PdfPreview />
  541. </div>
  542. </EditorProviders>
  543. )
  544. cy.findByRole('button', { name: 'Recompile' }).click()
  545. cy.waitForCompile({ pdf: true })
  546. cy.findByRole('button', { name: 'View logs' }).click()
  547. cy.findByRole('button', { name: 'Clear cached files' }).should(
  548. 'not.be.disabled'
  549. )
  550. const { promise, resolve } = Promise.withResolvers<void>()
  551. cy.intercept('DELETE', '/project/*/output*', req => {
  552. return promise
  553. .then(() => Cypress.Promise.delay(100))
  554. .then(() => {
  555. req.reply({ statusCode: 204 })
  556. })
  557. }).as('clear-cache')
  558. // click the button
  559. cy.findByRole('button', { name: 'Clear cached files' }).click()
  560. cy.findByRole('button', { name: 'Clear cached files' }).should(
  561. 'be.disabled'
  562. )
  563. cy.then(() => {
  564. resolve()
  565. })
  566. cy.wait('@clear-cache')
  567. cy.findByRole('button', { name: 'Clear cached files' }).should(
  568. 'not.be.disabled'
  569. )
  570. })
  571. it('handle "recompile from scratch"', function () {
  572. cy.interceptCompile()
  573. const scope = mockScope()
  574. cy.mount(
  575. <EditorProviders scope={scope}>
  576. <div className="pdf-viewer">
  577. <PdfPreview />
  578. </div>
  579. </EditorProviders>
  580. )
  581. cy.findByRole('button', { name: 'Recompile' }).click()
  582. cy.waitForCompile({ pdf: true })
  583. cy.interceptCompile({ prefix: 'recompile' })
  584. cy.intercept('DELETE', '/project/*/output*', {
  585. statusCode: 204,
  586. delay: 100,
  587. }).as('clear-cache')
  588. // show the logs UI
  589. cy.findByRole('button', { name: 'View logs' }).click()
  590. cy.findByRole('button', { name: 'Clear cached files' }).should(
  591. 'not.be.disabled'
  592. )
  593. cy.interceptDeferredCompile().then(resolveDeferredCompile => {
  594. cy.findByRole('button', { name: 'Toggle compile options menu' }).click()
  595. cy.findByRole('menuitem', {
  596. name: 'Recompile from scratch',
  597. }).trigger('click')
  598. cy.findByRole('button', { name: 'Clear cached files' }).should(
  599. 'be.disabled'
  600. )
  601. cy.wait('@clear-cache')
  602. cy.findByRole('button', { name: 'Compiling…' }).then(() =>
  603. resolveDeferredCompile()
  604. )
  605. // wait for recompile from scratch to finish
  606. cy.waitForCompile({ pdf: true })
  607. cy.findByRole('button', { name: 'Recompile' })
  608. })
  609. })
  610. })
  611. describe('invalid URLs and broken PDFs', function () {
  612. it('shows an error for an invalid URL', function () {
  613. cy.interceptCompile()
  614. cy.intercept('/build/*/output.pdf*', {
  615. statusCode: 500,
  616. body: {
  617. message: 'something awful happened',
  618. code: 'AWFUL_ERROR',
  619. },
  620. }).as('compile-pdf-error')
  621. const scope = mockScope()
  622. cy.mount(
  623. <EditorProviders scope={scope}>
  624. <div className="pdf-viewer">
  625. <PdfPreview />
  626. </div>
  627. </EditorProviders>
  628. )
  629. cy.findByRole('button', { name: 'Recompile' }).click()
  630. cy.waitForCompile()
  631. cy.wait('@compile-pdf-error')
  632. cy.contains('Something went wrong while rendering this PDF.')
  633. cy.contains(
  634. 'Please try recompiling the project from scratch, and if that doesn’t help, follow our troubleshooting guide.'
  635. )
  636. cy.findByLabelText('Page 1').should('not.exist')
  637. })
  638. it('shows an error for a corrupt PDF', function () {
  639. cy.interceptCompile()
  640. cy.intercept('/build/*/output.pdf*', {
  641. fixture: 'build/output-corrupt.pdf,null',
  642. }).as('compile-pdf-corrupt')
  643. const scope = mockScope()
  644. cy.mount(
  645. <EditorProviders scope={scope}>
  646. <div className="pdf-viewer">
  647. <PdfPreview />
  648. </div>
  649. </EditorProviders>
  650. )
  651. cy.findByRole('button', { name: 'Recompile' }).click()
  652. cy.waitForCompile()
  653. cy.wait('@compile-pdf-corrupt')
  654. cy.contains('Something went wrong while rendering this PDF.')
  655. cy.contains(
  656. 'Please try recompiling the project from scratch, and if that doesn’t help, follow our troubleshooting guide.'
  657. )
  658. cy.findByLabelText('Page 1').should('not.exist')
  659. })
  660. })
  661. describe('human readable logs', function () {
  662. it('shows human readable hint for undefined reference errors', function () {
  663. cy.interceptCompile()
  664. cy.intercept('/build/*/output.log*', {
  665. fixture: 'build/output-human-readable.log',
  666. }).as('compile-log')
  667. const scope = mockScope()
  668. cy.mount(
  669. <EditorProviders scope={scope}>
  670. <div className="pdf-viewer">
  671. <PdfPreview />
  672. </div>
  673. </EditorProviders>
  674. )
  675. cy.findByRole('button', { name: 'Recompile' }).click()
  676. cy.waitForCompile()
  677. cy.findByRole('button', { name: 'View logs' }).click()
  678. cy.findByText(
  679. "Reference `intorduction' on page 1 undefined on input line 11."
  680. )
  681. cy.findByText(
  682. "Reference `section1' on page 1 undefined on input line 13."
  683. )
  684. cy.findByText('There were undefined references.')
  685. cy.findAllByText(
  686. /You have referenced something which has not yet been labelled/
  687. ).should('have.length', 3)
  688. })
  689. it('does not show human readable hint when no undefined reference errors', function () {
  690. cy.interceptCompile()
  691. cy.intercept('/build/*/output.log?*', {
  692. fixture: 'build/output-undefined-references.log',
  693. }).as('compile-log')
  694. const scope = mockScope()
  695. cy.mount(
  696. <EditorProviders scope={scope}>
  697. <div className="pdf-viewer">
  698. <PdfPreview />
  699. </div>
  700. </EditorProviders>
  701. )
  702. cy.findByRole('button', { name: 'Recompile' }).click()
  703. cy.waitForCompile()
  704. cy.findByRole('button', { name: 'View logs' }).click()
  705. cy.findByText(
  706. "Package rerunfilecheck Warning: File `output.brf' has changed. Rerun to get bibliographical references right."
  707. )
  708. cy.findByText(
  709. /You have referenced something which has not yet been labelled/
  710. ).should('not.exist')
  711. })
  712. })
  713. })