Просмотр исходного кода

Improve compile request mocking in Cypress tests (#12095)

GitOrigin-RevId: fdbc53148e5437e451dab4889232923c823d649e
Alf Eaton 3 лет назад
Родитель
Сommit
488c6ff919

+ 104 - 19
services/web/cypress/support/shared/commands/compile.ts

@@ -43,26 +43,111 @@ const outputFiles = () => {
   ]
 }
 
-export const interceptCompile = (prefix = 'compile') => {
-  cy.intercept('POST', '/project/*/compile*', {
-    body: {
-      status: 'success',
-      clsiServerId: 'foo',
-      compileGroup: 'priority',
-      pdfDownloadDomain: 'https://clsi.test-overleaf.com',
-      outputFiles: outputFiles(),
-    },
-  }).as(`${prefix}`)
+export const interceptCompile = (prefix = 'compile', times = 1) => {
+  cy.intercept(
+    { method: 'POST', url: '/project/*/compile*', times },
+    {
+      body: {
+        status: 'success',
+        clsiServerId: 'foo',
+        compileGroup: 'priority',
+        pdfDownloadDomain: 'https://clsi.test-overleaf.com',
+        outputFiles: outputFiles(),
+      },
+    }
+  ).as(`${prefix}`)
+
+  cy.intercept(
+    { url: '/build/*/output.pdf*', times },
+    { fixture: 'build/output.pdf,null' }
+  ).as(`${prefix}-pdf`)
+
+  cy.intercept(
+    { url: '/build/*/output.log*', times },
+    { fixture: 'build/output.log' }
+  ).as(`${prefix}-log`)
+
+  cy.intercept(
+    { url: '/build/*/output.blg*', times },
+    { fixture: 'build/output.blg' }
+  ).as(`${prefix}-blg`)
+}
+
+export const waitForCompile = ({ prefix = 'compile', pdf = false } = {}) => {
+  cy.wait(`@${prefix}`)
+  cy.wait(`@${prefix}-log`)
+  cy.wait(`@${prefix}-blg`)
+  if (pdf) {
+    cy.wait(`@${prefix}-pdf`)
+  }
+  return cy.wrap(null)
+}
+
+export const interceptDeferredCompile = (beforeResponse?: () => void) => {
+  let resolveDeferredCompile: (value?: unknown) => void
+
+  const promise = new Promise(resolve => {
+    resolveDeferredCompile = resolve
+  })
+
+  cy.intercept(
+    { method: 'POST', url: '/project/*/compile*', times: 1 },
+    req => {
+      if (beforeResponse) {
+        beforeResponse()
+      }
+
+      // only reply once the Promise is resolved
+      promise.then(() => {
+        req.reply({
+          body: {
+            status: 'success',
+            clsiServerId: 'foo',
+            compileGroup: 'priority',
+            pdfDownloadDomain: 'https://clsi.test-overleaf.com',
+            outputFiles: [
+              {
+                path: 'output.pdf',
+                build: '123',
+                url: '/build/123/output.pdf',
+                type: 'pdf',
+              },
+              {
+                path: 'output.log',
+                build: '123',
+                url: '/build/123/output.log',
+                type: 'log',
+              },
+              {
+                path: 'output.blg',
+                build: '123',
+                url: '/build/123/output.blg',
+                type: 'log',
+              },
+            ],
+          },
+        })
+      })
+
+      return promise
+    }
+  ).as('compile')
+
+  cy.intercept(
+    { url: '/build/*/output.pdf*', times: 1 },
+    { fixture: 'build/output.pdf,null' }
+  ).as(`compile-pdf`)
 
-  cy.intercept('/build/*/output.pdf*', {
-    fixture: 'build/output.pdf,null',
-  }).as(`${prefix}-pdf`)
+  cy.intercept(
+    { url: '/build/*/output.log*', times: 1 },
+    { fixture: 'build/output.log' }
+  ).as(`compile-log`)
 
-  cy.intercept('/build/*/output.log*', {
-    fixture: 'build/output.log',
-  }).as(`${prefix}-log`)
+  cy.intercept(
+    { url: '/build/*/output.blg*', times: 1 },
+    { fixture: 'build/output.blg' }
+  ).as(`compile-blg`)
 
-  cy.intercept('/build/*/output.blg*', {
-    fixture: 'build/output.blg',
-  }).as(`${prefix}-blg`)
+  // @ts-ignore
+  return cy.wrap(resolveDeferredCompile)
 }

+ 9 - 1
services/web/cypress/support/shared/commands/index.ts

@@ -1,5 +1,9 @@
 import '@testing-library/cypress/add-commands'
-import { interceptCompile } from './compile'
+import {
+  interceptCompile,
+  waitForCompile,
+  interceptDeferredCompile,
+} from './compile'
 import { interceptEvents } from './events'
 import { interceptSpelling } from './spelling'
 
@@ -12,6 +16,8 @@ declare global {
       interceptCompile: typeof interceptCompile
       interceptEvents: typeof interceptEvents
       interceptSpelling: typeof interceptSpelling
+      waitForCompile: typeof waitForCompile
+      interceptDeferredCompile: typeof interceptDeferredCompile
       index: () => Chainable<number>
     }
   }
@@ -20,6 +26,8 @@ declare global {
 Cypress.Commands.add('interceptCompile', interceptCompile)
 Cypress.Commands.add('interceptEvents', interceptEvents)
 Cypress.Commands.add('interceptSpelling', interceptSpelling)
+Cypress.Commands.add('waitForCompile', waitForCompile)
+Cypress.Commands.add('interceptDeferredCompile', interceptDeferredCompile)
 Cypress.Commands.add('index', { prevSubject: true }, subject => {
   return cy.wrap(subject).invoke('index')
 })

+ 13 - 1
services/web/test/frontend/components/pdf-preview/detach-compile-button.spec.tsx

@@ -5,7 +5,7 @@ import { testDetachChannel } from '../../helpers/detach-channel'
 
 describe('<DetachCompileButtonWrapper />', function () {
   beforeEach(function () {
-    cy.interceptCompile()
+    window.metaAttributesCache = new Map()
     cy.interceptEvents()
   })
 
@@ -14,6 +14,8 @@ describe('<DetachCompileButtonWrapper />', function () {
   })
 
   it('detacher mode and not linked: does not show button ', function () {
+    cy.interceptCompile()
+
     cy.window().then(win => {
       win.metaAttributesCache = new Map([['ol-detachRole', 'detacher']])
     })
@@ -26,10 +28,14 @@ describe('<DetachCompileButtonWrapper />', function () {
       </EditorProviders>
     )
 
+    cy.waitForCompile()
+
     cy.findByRole('button', { name: 'Recompile' }).should('not.exist')
   })
 
   it('detacher mode and linked: show button', function () {
+    cy.interceptCompile()
+
     cy.window().then(win => {
       win.metaAttributesCache = new Map([['ol-detachRole', 'detacher']])
     })
@@ -42,6 +48,8 @@ describe('<DetachCompileButtonWrapper />', function () {
       </EditorProviders>
     )
 
+    cy.waitForCompile()
+
     cy.wrap(null).then(() => {
       testDetachChannel.postMessage({
         role: 'detached',
@@ -53,6 +61,8 @@ describe('<DetachCompileButtonWrapper />', function () {
   })
 
   it('not detacher mode and linked: does not show button ', function () {
+    cy.interceptCompile()
+
     cy.window().then(win => {
       win.metaAttributesCache = new Map([['ol-detachRole', 'detached']])
     })
@@ -65,6 +75,8 @@ describe('<DetachCompileButtonWrapper />', function () {
       </EditorProviders>
     )
 
+    cy.waitForCompile()
+
     cy.wrap(null).then(() => {
       testDetachChannel.postMessage({
         role: 'detacher',

+ 16 - 1
services/web/test/frontend/components/pdf-preview/pdf-js-viewer.spec.tsx

@@ -6,11 +6,12 @@ import { unmountComponentAtNode } from 'react-dom'
 
 describe('<PdfJSViewer/>', function () {
   beforeEach(function () {
-    cy.interceptCompile()
     cy.interceptEvents()
   })
 
   it('loads all PDF pages', function () {
+    cy.interceptCompile()
+
     const scope = mockScope()
 
     cy.mount(
@@ -21,6 +22,8 @@ describe('<PdfJSViewer/>', function () {
       </EditorProviders>
     )
 
+    cy.waitForCompile()
+
     cy.findByLabelText('Page 1')
     cy.findByLabelText('Page 2')
     cy.findByLabelText('Page 3')
@@ -30,6 +33,8 @@ describe('<PdfJSViewer/>', function () {
   })
 
   it('renders pages in a "loading" state', function () {
+    cy.interceptCompile()
+
     const scope = mockScope()
 
     cy.mount(
@@ -40,10 +45,14 @@ describe('<PdfJSViewer/>', function () {
       </EditorProviders>
     )
 
+    cy.waitForCompile()
+
     cy.findByLabelText('Loading…')
   })
 
   it('can be unmounted while loading a document', function () {
+    cy.interceptCompile()
+
     const scope = mockScope()
 
     cy.mount(
@@ -54,10 +63,14 @@ describe('<PdfJSViewer/>', function () {
       </EditorProviders>
     )
 
+    cy.waitForCompile()
+
     cy.then(() => unmountComponentAtNode(getContainerEl()))
   })
 
   it('can be unmounted after loading a document', function () {
+    cy.interceptCompile()
+
     const scope = mockScope()
 
     cy.mount(
@@ -68,6 +81,8 @@ describe('<PdfJSViewer/>', function () {
       </EditorProviders>
     )
 
+    cy.waitForCompile()
+
     cy.findByLabelText('Page 1')
 
     cy.then(() => unmountComponentAtNode(getContainerEl()))

+ 5 - 1
services/web/test/frontend/components/pdf-preview/pdf-preview-detached-root.spec.tsx

@@ -11,9 +11,9 @@ describe('<PdfPreviewDetachedRoot/>', function () {
       ['ol-project_id', 'project1'],
       ['ol-detachRole', 'detached'],
       ['ol-projectName', 'Project Name'],
+      ['ol-preventCompileOnLoad', true],
     ])
 
-    cy.interceptCompile()
     cy.interceptEvents()
   })
 
@@ -22,6 +22,8 @@ describe('<PdfPreviewDetachedRoot/>', function () {
   })
 
   it('syncs compiling state', function () {
+    cy.interceptCompile()
+
     cy.mount(<PdfPreviewDetachedRoot />)
 
     cy.wrap(null).then(() => {
@@ -51,6 +53,8 @@ describe('<PdfPreviewDetachedRoot/>', function () {
   })
 
   it('sends a clear cache request when the button is pressed', function () {
+    cy.interceptCompile()
+
     cy.mount(<PdfPreviewDetachedRoot />)
 
     cy.wrap(null).then(() => {

+ 2 - 1
services/web/test/frontend/components/pdf-preview/pdf-preview-hybrid-toolbar.spec.tsx

@@ -4,7 +4,8 @@ import { testDetachChannel } from '../../helpers/detach-channel'
 
 describe('<PdfPreviewHybridToolbar/>', function () {
   beforeEach(function () {
-    cy.interceptCompile()
+    window.metaAttributesCache = new Map()
+    window.metaAttributesCache.set('ol-preventCompileOnLoad', true)
     cy.interceptEvents()
   })
 

+ 186 - 209
services/web/test/frontend/components/pdf-preview/pdf-preview.spec.tsx

@@ -22,11 +22,18 @@ const Layout: FC<{ layout: string; view?: string }> = ({ layout, view }) => {
 
 describe('<PdfPreview/>', function () {
   beforeEach(function () {
-    cy.interceptCompile()
+    window.metaAttributesCache.set('ol-preventCompileOnLoad', true)
     cy.interceptEvents()
   })
 
+  afterEach(function () {
+    window.metaAttributesCache = new Map()
+  })
+
   it('renders the PDF preview', function () {
+    window.metaAttributesCache.set('ol-preventCompileOnLoad', false)
+    cy.interceptCompile('compile')
+
     const scope = mockScope()
 
     cy.mount(
@@ -38,13 +45,14 @@ describe('<PdfPreview/>', function () {
     )
 
     // wait for "compile on load" to finish
-    cy.findByRole('button', { name: 'Compiling…' })
-    cy.wait('@compile')
+    cy.waitForCompile({ pdf: true })
+
     cy.findByRole('button', { name: 'Recompile' })
-    cy.wait('@compile-pdf')
   })
 
   it('runs a compile when the Recompile button is pressed', function () {
+    cy.interceptCompile()
+
     const scope = mockScope()
 
     cy.mount(
@@ -55,28 +63,18 @@ describe('<PdfPreview/>', function () {
       </EditorProviders>
     )
 
-    // wait for "compile on load" to finish
-    cy.findByRole('button', { name: 'Compiling…' })
-    cy.wait('@compile')
-    cy.wait('@compile-pdf')
-
-    cy.interceptCompile('recompile')
-
     // press the Recompile button => compile
     cy.findByRole('button', { name: 'Recompile' }).click()
 
-    // wait for "recompile" to finish
-    // cy.findByRole('button', { name: 'Compiling…' })
-    cy.wait('@recompile-pdf')
-    cy.wait('@recompile-log')
-    cy.wait('@recompile-blg')
-
-    cy.findByRole('button', { name: 'Recompile' })
+    // wait for compile to finish
+    cy.waitForCompile({ pdf: true })
 
     cy.contains('Your Paper')
   })
 
   it('runs a compile on `pdf:recompile` event', function () {
+    cy.interceptCompile()
+
     const scope = mockScope()
 
     cy.mount(
@@ -87,111 +85,79 @@ describe('<PdfPreview/>', function () {
       </EditorProviders>
     )
 
-    // wait for "compile on load" to finish
-    cy.findByRole('button', { name: 'Compiling…' })
-    cy.wait('@compile')
-    cy.wait('@compile-pdf')
-
-    cy.interceptCompile('recompile')
-
     cy.window().then(win => {
       win.dispatchEvent(new CustomEvent('pdf:recompile'))
     })
 
-    // wait for "recompile" to finish
-    // cy.findByRole('button', { name: 'Compiling…' })
-    cy.wait('@recompile')
+    // wait for compile to finish
+    cy.waitForCompile({ pdf: true })
 
-    cy.findByRole('button', { name: 'Recompile' })
-
-    cy.wait('@recompile-pdf')
     cy.contains('Your Paper')
   })
 
   it('does not compile while compiling', function () {
-    let compileResolve: (value?: unknown) => void
     let counter = 0
+    cy.interceptDeferredCompile(() => counter++).then(
+      resolveDeferredCompile => {
+        const scope = mockScope()
 
-    const promise = new Promise(resolve => {
-      compileResolve = resolve
-    })
-
-    cy.intercept(
-      'POST',
-      '/project/project123/compile?auto_compile=true',
-      req => {
-        counter++
-
-        promise.then(() => {
-          req.reply({
-            body: {
-              status: 'success',
-              clsiServerId: 'foo',
-              compileGroup: 'priority',
-              pdfDownloadDomain: 'https://clsi.test-overleaf.com',
-              outputFiles: [
-                {
-                  path: 'output.pdf',
-                  build: '123',
-                  url: '/build/123/output.pdf',
-                  type: 'pdf',
-                },
-                {
-                  path: 'output.log',
-                  build: '123',
-                  url: '/build/123/output.log',
-                  type: 'log',
-                },
-              ],
-            },
-          })
-        })
-
-        return promise
-      }
-    ).as('compile')
+        cy.mount(
+          <EditorProviders scope={scope}>
+            <div className="pdf-viewer">
+              <PdfPreview />
+            </div>
+          </EditorProviders>
+        )
 
-    const scope = mockScope()
+        // start compiling
+        cy.findByRole('button', { name: 'Recompile' })
+          .click()
+          .then(() => {
+            cy.findByRole('button', { name: 'Compiling…' })
 
-    cy.mount(
-      <EditorProviders scope={scope}>
-        <div className="pdf-viewer">
-          <PdfPreview />
-        </div>
-      </EditorProviders>
-    ).then(() => {
-      cy.findByRole('button', { name: 'Compiling…' })
+            // trigger a recompile
+            cy.window().then(win => {
+              win.dispatchEvent(new CustomEvent('pdf:recompile'))
+            })
 
-      cy.window().then(win => {
-        win.dispatchEvent(new CustomEvent('pdf:recompile'))
-      })
+            // finish the original compile
+            resolveDeferredCompile()
 
-      compileResolve()
+            // wait for the original compile to finish
+            cy.waitForCompile({ pdf: true })
 
-      cy.findByRole('button', { name: 'Recompile' })
-
-      cy.contains('Your Paper').should(() => {
-        expect(counter).to.equal(1)
-      })
-    })
+            // NOTE: difficult to assert that a second request won't be sent, at some point
+            expect(counter).to.equal(1)
+          })
+      }
+    )
   })
 
   it('disables compile button while compile is running', function () {
-    const scope = mockScope()
+    cy.interceptDeferredCompile().then(resolveDeferredCompile => {
+      const scope = mockScope()
 
-    cy.mount(
-      <EditorProviders scope={scope}>
-        <div className="pdf-viewer">
-          <PdfPreview />
-        </div>
-      </EditorProviders>
-    )
+      cy.mount(
+        <EditorProviders scope={scope}>
+          <div className="pdf-viewer">
+            <PdfPreview />
+          </div>
+        </EditorProviders>
+      )
 
-    cy.findByRole('button', { name: 'Compiling…' }).should('be.disabled')
-    cy.findByRole('button', { name: 'Recompile' }).should('not.be.disabled')
+      cy.findByRole('button', { name: 'Recompile' }).click()
+      cy.findByRole('button', { name: 'Compiling…' })
+        .should('be.disabled')
+        .then(resolveDeferredCompile)
+
+      cy.waitForCompile()
+      cy.findByRole('button', { name: 'Recompile' }).should('not.be.disabled')
+    })
   })
 
   it('runs a compile on doc change if autocompile is enabled', function () {
+    cy.interceptCompile()
+
     const scope = mockScope()
 
     cy.mount(
@@ -202,11 +168,6 @@ describe('<PdfPreview/>', function () {
       </EditorProviders>
     )
 
-    // wait for "compile on load" to finish
-    cy.findByRole('button', { name: 'Compiling…' })
-    cy.wait('@compile')
-    cy.findByRole('button', { name: 'Recompile' })
-
     cy.window().then(win => {
       cy.clock()
 
@@ -216,16 +177,21 @@ describe('<PdfPreview/>', function () {
       // fire a doc:changed event => compile
       win.dispatchEvent(new CustomEvent('doc:changed'))
 
+      // wait enough time for the compile to start
       cy.tick(6000) // > AUTO_COMPILE_DEBOUNCE
 
       cy.clock().invoke('restore')
     })
 
-    cy.findByRole('button', { name: 'Compiling…' })
+    // wait for compile to finish
+    cy.waitForCompile({ pdf: true })
+
     cy.findByRole('button', { name: 'Recompile' })
   })
 
   it('does not run a compile on doc change if autocompile is disabled', function () {
+    cy.interceptCompile()
+
     const scope = mockScope()
 
     cy.mount(
@@ -236,10 +202,6 @@ describe('<PdfPreview/>', function () {
       </EditorProviders>
     )
 
-    // wait for "compile on load" to finish
-    cy.findByRole('button', { name: 'Compiling…' })
-    cy.findByRole('button', { name: 'Recompile' })
-
     cy.window().then(win => {
       cy.clock()
 
@@ -249,15 +211,19 @@ describe('<PdfPreview/>', function () {
       // fire a doc:changed event => no compile
       win.dispatchEvent(new CustomEvent('doc:changed'))
 
-      cy.tick(5000) // AUTO_COMPILE_DEBOUNCE
+      // wait enough time for the compile to start
+      cy.tick(6000) // AUTO_COMPILE_DEBOUNCE
 
       cy.clock().invoke('restore')
     })
 
+    // NOTE: difficult to assert that a request hasn't been sent
     cy.findByRole('button', { name: 'Recompile' })
   })
 
   it('does not run a compile on doc change if autocompile is blocked by syntax check', function () {
+    cy.interceptCompile()
+
     const scope = mockScope()
     // enable linting in the editor
     scope.settings.syntaxValidation = true
@@ -272,10 +238,6 @@ describe('<PdfPreview/>', function () {
       </EditorProviders>
     )
 
-    // wait for "compile on load" to finish
-    cy.findByRole('button', { name: 'Compiling…' })
-    cy.findByRole('button', { name: 'Recompile' })
-
     cy.window().then(win => {
       cy.clock()
 
@@ -288,16 +250,21 @@ describe('<PdfPreview/>', function () {
       // fire a doc:changed event => no compile
       win.dispatchEvent(new CustomEvent('doc:changed'))
 
-      cy.tick(5000) // AUTO_COMPILE_DEBOUNCE
+      // wait enough time for the compile to start
+      cy.tick(6000) // AUTO_COMPILE_DEBOUNCE
 
       cy.clock().invoke('restore')
     })
 
+    // NOTE: difficult to assert that a request hasn't been sent
     cy.findByRole('button', { name: 'Recompile' })
+
     cy.findByText('Code check failed')
   })
 
   it('does not run a compile on doc change if the PDF preview is not open', function () {
+    cy.interceptCompile()
+
     const scope = mockScope()
 
     cy.mount(
@@ -309,11 +276,6 @@ describe('<PdfPreview/>', function () {
       </EditorProviders>
     )
 
-    // wait for "compile on load" to finish
-    cy.findByRole('button', { name: 'Compiling…' })
-    cy.wait('@compile')
-    cy.findByRole('button', { name: 'Recompile' })
-
     cy.window().then(win => {
       cy.clock()
 
@@ -323,15 +285,17 @@ describe('<PdfPreview/>', function () {
       // fire a doc:changed event => compile
       win.dispatchEvent(new CustomEvent('doc:changed'))
 
+      // wait enough time for the compile to start
       cy.tick(6000) // > AUTO_COMPILE_DEBOUNCE
 
       cy.clock().invoke('restore')
     })
 
+    // NOTE: difficult to assert that a request hasn't been sent
     cy.findByRole('button', { name: 'Recompile' })
   })
 
-  describe('displays error messages', function () {
+  describe('error messages', function () {
     const compileErrorStatuses = {
       'clear-cache':
         'Sorry, something went wrong and your project could not be compiled. Please try again in a few moments.',
@@ -355,7 +319,7 @@ describe('<PdfPreview/>', function () {
 
     for (const [status, message] of Object.entries(compileErrorStatuses)) {
       it(`displays error message for '${status}' status`, function () {
-        cy.intercept('POST', '/project/*/compile?*', {
+        cy.intercept('POST', '/project/*/compile*', {
           body: {
             status,
             clsiServerId: 'foo',
@@ -373,86 +337,86 @@ describe('<PdfPreview/>', function () {
           </EditorProviders>
         )
 
-        // wait for "compile on load" to finish
-        cy.findByRole('button', { name: 'Compiling…' })
-        cy.findByRole('button', { name: 'Recompile' })
-
+        cy.findByRole('button', { name: 'Recompile' }).click()
+        cy.wait('@compile')
         cy.findByText(message)
       })
     }
+  })
 
-    it('displays expandable raw logs', function () {
-      const scope = mockScope()
+  it('displays expandable raw logs', function () {
+    cy.interceptCompile()
 
-      cy.mount(
-        <EditorProviders scope={scope}>
-          <div className="pdf-viewer">
-            <PdfPreview />
-          </div>
-        </EditorProviders>
-      )
+    const scope = mockScope()
 
-      // wait for "compile on load" to finish
-      cy.findByRole('button', { name: 'Compiling…' })
-      cy.findByRole('button', { name: 'Recompile' })
+    cy.mount(
+      <EditorProviders scope={scope}>
+        <div className="pdf-viewer">
+          <PdfPreview />
+        </div>
+      </EditorProviders>
+    )
 
-      cy.findByRole('button', { name: 'View logs' }).click()
-      cy.findByRole('button', { name: 'View PDF' })
+    cy.findByRole('button', { name: 'Recompile' }).click()
+    cy.waitForCompile({ pdf: true })
 
-      cy.findByRole('button', { name: 'Expand' }).click()
-      cy.findByRole('button', { name: 'Collapse' }).click()
-    })
+    cy.findByRole('button', { name: 'View logs' }).click()
+    cy.findByRole('button', { name: 'View PDF' })
 
-    it('displays error messages if there were validation problems', function () {
-      const validationProblems = {
-        sizeCheck: {
-          resources: [
-            { path: 'foo/bar', kbSize: 76221 },
-            { path: 'bar/baz', kbSize: 2342 },
-          ],
-        },
-        mainFile: true,
-        conflictedPaths: [
-          {
-            path: 'foo/bar',
-          },
-          {
-            path: 'foo/baz',
-          },
-        ],
-      }
+    cy.findByRole('button', { name: 'Expand' }).click()
+    cy.findByRole('button', { name: 'Collapse' }).click()
+  })
 
-      cy.intercept('POST', '/project/*/compile?*', {
-        body: {
-          status: 'validation-problems',
-          validationProblems,
-          clsiServerId: 'foo',
-          compileGroup: 'priority',
+  it('displays error messages if there were validation problems', function () {
+    const validationProblems = {
+      sizeCheck: {
+        resources: [
+          { path: 'foo/bar', kbSize: 76221 },
+          { path: 'bar/baz', kbSize: 2342 },
+        ],
+      },
+      mainFile: true,
+      conflictedPaths: [
+        {
+          path: 'foo/bar',
         },
-      }).as('compile')
+        {
+          path: 'foo/baz',
+        },
+      ],
+    }
 
-      const scope = mockScope()
+    cy.intercept('POST', '/project/*/compile*', {
+      body: {
+        status: 'validation-problems',
+        validationProblems,
+        clsiServerId: 'foo',
+        compileGroup: 'priority',
+      },
+    }).as('compile')
 
-      cy.mount(
-        <EditorProviders scope={scope}>
-          <div className="pdf-viewer">
-            <PdfPreview />
-          </div>
-        </EditorProviders>
-      )
+    const scope = mockScope()
 
-      // wait for "compile on load" to finish
-      cy.findByRole('button', { name: 'Compiling…' })
-      cy.findByRole('button', { name: 'Recompile' })
+    cy.mount(
+      <EditorProviders scope={scope}>
+        <div className="pdf-viewer">
+          <PdfPreview />
+        </div>
+      </EditorProviders>
+    )
 
-      cy.wait('@compile')
+    cy.findByRole('button', { name: 'Recompile' }).click()
+    cy.wait('@compile')
 
-      cy.findByText('Project too large')
-      cy.findByText('Unknown main document')
-      cy.findByText('Conflicting Paths Found')
-    })
+    cy.findByText('Project too large')
+    cy.findByText('Unknown main document')
+    cy.findByText('Conflicting Paths Found')
+  })
 
+  describe('clear cache', function () {
     it('sends a clear cache request when the button is pressed', function () {
+      cy.interceptCompile()
+
       const scope = mockScope()
 
       cy.mount(
@@ -463,16 +427,15 @@ describe('<PdfPreview/>', function () {
         </EditorProviders>
       )
 
-      // wait for "compile on load" to finish
-      cy.findByRole('button', { name: 'Compiling…' })
-      cy.findByRole('button', { name: 'Recompile' })
+      cy.findByRole('button', { name: 'Recompile' }).click()
+      cy.waitForCompile({ pdf: true })
 
       cy.findByRole('button', { name: 'View logs' }).click()
       cy.findByRole('button', { name: 'Clear cached files' }).should(
         'not.be.disabled'
       )
 
-      cy.intercept('DELETE', 'project/*/output?*', {
+      cy.intercept('DELETE', '/project/*/output*', {
         statusCode: 204,
         delay: 100,
       }).as('clear-cache')
@@ -489,6 +452,8 @@ describe('<PdfPreview/>', function () {
     })
 
     it('handle "recompile from scratch"', function () {
+      cy.interceptCompile()
+
       const scope = mockScope()
 
       cy.mount(
@@ -499,10 +464,13 @@ describe('<PdfPreview/>', function () {
         </EditorProviders>
       )
 
-      // wait for "compile on load" to finish
-      cy.findByRole('button', { name: 'Compiling…' })
-      cy.wait('@compile')
-      cy.findByRole('button', { name: 'Recompile' })
+      cy.findByRole('button', { name: 'Recompile' }).click()
+      cy.waitForCompile({ pdf: true })
+      cy.interceptCompile('recompile')
+      cy.intercept('DELETE', '/project/*/output*', {
+        statusCode: 204,
+        delay: 100,
+      }).as('clear-cache')
 
       // show the logs UI
       cy.findByRole('button', { name: 'View logs' }).click()
@@ -511,13 +479,6 @@ describe('<PdfPreview/>', function () {
         'not.be.disabled'
       )
 
-      cy.interceptCompile()
-
-      cy.intercept('DELETE', 'project/*/output?*', {
-        statusCode: 204,
-        delay: 100,
-      }).as('clear-cache')
-
       // TODO: open the menu?
       cy.findByRole('menuitem', {
         name: 'Recompile from scratch',
@@ -530,14 +491,19 @@ describe('<PdfPreview/>', function () {
 
       cy.findByRole('button', { name: 'Compiling…' })
       cy.wait('@clear-cache')
-      cy.findByRole('button', { name: 'Recompile' })
 
-      cy.wait('@compile')
-      cy.wait('@compile-pdf')
+      // wait for recompile from scratch to finish
+      cy.waitForCompile({ pdf: true, prefix: 'recompile' })
+
+      cy.findByRole('button', { name: 'Recompile' })
     })
+  })
 
+  describe('invalid URLs and broken PDFs', function () {
     it('shows an error for an invalid URL', function () {
-      cy.intercept('/build/*/output.pdf?*', {
+      cy.interceptCompile()
+
+      cy.intercept('/build/*/output.pdf*', {
         statusCode: 500,
         body: {
           message: 'something awful happened',
@@ -555,6 +521,8 @@ describe('<PdfPreview/>', function () {
         </EditorProviders>
       )
 
+      cy.findByRole('button', { name: 'Recompile' }).click()
+      cy.waitForCompile()
       cy.wait('@compile-pdf-error')
 
       cy.contains('Something went wrong while rendering this PDF.')
@@ -565,7 +533,9 @@ describe('<PdfPreview/>', function () {
     })
 
     it('shows an error for a corrupt PDF', function () {
-      cy.intercept('/build/*/output.pdf?*', {
+      cy.interceptCompile()
+
+      cy.intercept('/build/*/output.pdf*', {
         fixture: 'build/output-corrupt.pdf,null',
       }).as('compile-pdf-corrupt')
 
@@ -579,6 +549,8 @@ describe('<PdfPreview/>', function () {
         </EditorProviders>
       )
 
+      cy.findByRole('button', { name: 'Recompile' }).click()
+      cy.waitForCompile()
       cy.wait('@compile-pdf-corrupt')
 
       cy.contains('Something went wrong while rendering this PDF.')
@@ -591,9 +563,11 @@ describe('<PdfPreview/>', function () {
 
   describe('human readable logs', function () {
     it('shows human readable hint for undefined reference errors', function () {
-      cy.intercept('/build/*/output.log?*', {
+      cy.interceptCompile()
+
+      cy.intercept('/build/*/output.log*', {
         fixture: 'build/output-human-readable.log',
-      }).as('log')
+      }).as('compile-log')
 
       const scope = mockScope()
 
@@ -605,7 +579,8 @@ describe('<PdfPreview/>', function () {
         </EditorProviders>
       )
 
-      cy.wait('@log')
+      cy.findByRole('button', { name: 'Recompile' }).click()
+      cy.waitForCompile()
       cy.findByRole('button', { name: 'View logs' }).click()
 
       cy.findByText(
@@ -622,9 +597,10 @@ describe('<PdfPreview/>', function () {
     })
 
     it('does not show human readable hint when no undefined reference errors', function () {
+      cy.interceptCompile()
       cy.intercept('/build/*/output.log?*', {
         fixture: 'build/output-undefined-references.log',
-      }).as('log')
+      }).as('compile-log')
 
       const scope = mockScope()
 
@@ -636,7 +612,8 @@ describe('<PdfPreview/>', function () {
         </EditorProviders>
       )
 
-      cy.wait('@log')
+      cy.findByRole('button', { name: 'Recompile' }).click()
+      cy.waitForCompile()
       cy.findByRole('button', { name: 'View logs' }).click()
 
       cy.findByText(

+ 39 - 12
services/web/test/frontend/components/pdf-preview/pdf-synctex-controls.spec.tsx

@@ -77,7 +77,7 @@ const WithSelectedEntities = ({
 }
 
 const interceptSyncCodeAsync = () => {
-  const output: { resolve: () => void } = {
+  const deferred: { resolve: () => void } = {
     resolve: () => {
       // do nothing
     },
@@ -85,7 +85,7 @@ const interceptSyncCodeAsync = () => {
 
   cy.intercept('/project/*/sync/code?*', req => {
     return new Promise(resolve => {
-      output.resolve = () => {
+      deferred.resolve = () => {
         req.reply({
           body: { pdf: cloneDeep(mockHighlights) },
         })
@@ -94,7 +94,7 @@ const interceptSyncCodeAsync = () => {
     })
   }).as('sync-code')
 
-  return output
+  return deferred
 }
 
 const interceptSyncPdfAsync = () => {
@@ -127,12 +127,10 @@ const interceptSyncPdf = () => {
   }).as('sync-pdf')
 }
 
-// eslint-disable-next-line mocha/no-skipped-tests
-describe.skip('<PdfSynctexControls/>', function () {
+describe('<PdfSynctexControls/>', function () {
   beforeEach(function () {
     window.metaAttributesCache = new Map()
-
-    cy.interceptCompile()
+    window.metaAttributesCache.set('ol-preventCompileOnLoad', false)
     cy.interceptEvents()
   })
 
@@ -141,6 +139,8 @@ describe.skip('<PdfSynctexControls/>', function () {
   })
 
   it('handles clicks on sync buttons', function () {
+    cy.interceptCompile()
+
     const scope = mockScope()
 
     cy.mount(
@@ -151,6 +151,8 @@ describe.skip('<PdfSynctexControls/>', function () {
       </EditorProviders>
     )
 
+    cy.waitForCompile()
+
     cy.get('.synctex-control-icon').should('have.length', 2)
 
     // mock editor cursor position update
@@ -162,7 +164,7 @@ describe.skip('<PdfSynctexControls/>', function () {
       )
     })
 
-    cy.wait('@compile').then(() => {
+    cy.wrap(null).then(() => {
       setDetachedPosition(mockPosition)
     })
 
@@ -190,6 +192,8 @@ describe.skip('<PdfSynctexControls/>', function () {
   })
 
   it('disables button when multiple entities are selected', function () {
+    cy.interceptCompile()
+
     const scope = mockScope()
 
     cy.mount(
@@ -202,12 +206,16 @@ describe.skip('<PdfSynctexControls/>', function () {
       </EditorProviders>
     )
 
+    cy.waitForCompile()
+
     cy.findByRole('button', { name: 'Go to code location in PDF' }).should(
       'be.disabled'
     )
   })
 
   it('disables button when a file is selected', function () {
+    cy.interceptCompile()
+
     const scope = mockScope()
 
     cy.mount(
@@ -218,6 +226,8 @@ describe.skip('<PdfSynctexControls/>', function () {
       </EditorProviders>
     )
 
+    cy.waitForCompile()
+
     cy.findByRole('button', { name: 'Go to code location in PDF' }).should(
       'be.disabled'
     )
@@ -229,6 +239,8 @@ describe.skip('<PdfSynctexControls/>', function () {
     })
 
     it('does not have go to PDF location button nor arrow icon', function () {
+      cy.interceptCompile()
+
       const scope = mockScope()
 
       cy.mount(
@@ -239,6 +251,8 @@ describe.skip('<PdfSynctexControls/>', function () {
         </EditorProviders>
       )
 
+      cy.waitForCompile()
+
       cy.findByRole('button', { name: /^Go to PDF location in code/ }).should(
         'not.exist'
       )
@@ -247,6 +261,8 @@ describe.skip('<PdfSynctexControls/>', function () {
     })
 
     it('send set highlights action', function () {
+      cy.interceptCompile()
+
       const scope = mockScope()
 
       cy.mount(
@@ -257,7 +273,7 @@ describe.skip('<PdfSynctexControls/>', function () {
         </EditorProviders>
       )
 
-      cy.wait('@compile')
+      cy.waitForCompile()
 
       // mock editor cursor position update
       cy.window().then(win => {
@@ -302,6 +318,7 @@ describe.skip('<PdfSynctexControls/>', function () {
     })
 
     it('reacts to sync to code action', function () {
+      cy.interceptCompile()
       interceptSyncPdf()
 
       const scope = mockScope()
@@ -312,7 +329,9 @@ describe.skip('<PdfSynctexControls/>', function () {
           <WithSelectedEntities mockSelectedEntities={mockSelectedEntities} />
           <PdfSynctexControls />
         </EditorProviders>
-      ).then(() => {
+      )
+
+      cy.waitForCompile().then(() => {
         testDetachChannel.postMessage({
           role: 'detached',
           event: 'action-sync-to-code',
@@ -332,6 +351,8 @@ describe.skip('<PdfSynctexControls/>', function () {
     })
 
     it('does not have go to code location button nor arrow icon', function () {
+      cy.interceptCompile()
+
       const scope = mockScope()
 
       cy.mount(
@@ -341,6 +362,8 @@ describe.skip('<PdfSynctexControls/>', function () {
         </EditorProviders>
       )
 
+      cy.waitForCompile()
+
       cy.findByRole('button', {
         name: 'Go to code location in PDF',
       }).should('not.exist')
@@ -349,6 +372,8 @@ describe.skip('<PdfSynctexControls/>', function () {
     })
 
     it('send go to code line action', function () {
+      cy.interceptCompile()
+
       const scope = mockScope()
 
       cy.mount(
@@ -357,7 +382,7 @@ describe.skip('<PdfSynctexControls/>', function () {
         </EditorProviders>
       )
 
-      cy.wait('@compile').then(() => {
+      cy.waitForCompile().then(() => {
         testDetachChannel.postMessage({
           role: 'detacher',
           event: `state-position`,
@@ -394,6 +419,8 @@ describe.skip('<PdfSynctexControls/>', function () {
     })
 
     it('update inflight state', function () {
+      cy.interceptCompile()
+
       const scope = mockScope()
 
       cy.mount(
@@ -403,7 +430,7 @@ describe.skip('<PdfSynctexControls/>', function () {
         </EditorProviders>
       )
 
-      cy.wrap(null).then(() => {
+      cy.waitForCompile().then(() => {
         testDetachChannel.postMessage({
           role: 'detacher',
           event: `state-position`,

+ 3 - 2
services/web/test/frontend/shared/hooks/use-detach-layout.spec.tsx

@@ -34,12 +34,13 @@ const DetachLayoutTest = () => {
   )
 }
 
-// eslint-disable-next-line mocha/no-skipped-tests
-describe.skip('useDetachLayout', function () {
+describe('useDetachLayout', function () {
   beforeEach(function () {
     window.metaAttributesCache = new Map()
+    window.metaAttributesCache.set('ol-preventCompileOnLoad', true)
     cy.stub(window, 'open').as('openWindow')
     cy.stub(window, 'close').as('closeWindow')
+    cy.interceptEvents()
   })
 
   afterEach(function () {