import localStorage from '../../../../frontend/js/infrastructure/local-storage' import PdfPreview from '../../../../frontend/js/features/pdf-preview/components/pdf-preview' import { EditorProviders } from '../../helpers/editor-providers' import { mockScope } from './scope' const storeAndFireEvent = (win: typeof window, key: string, value: unknown) => { localStorage.setItem(key, value) win.dispatchEvent(new StorageEvent('storage', { key })) } describe('', function () { beforeEach(function () { cy.interceptCompile() cy.interceptEvents() }) it('renders the PDF preview', function () { const scope = mockScope() cy.mount(
) // wait for "compile on load" to finish cy.findByRole('button', { name: 'Compiling…' }) cy.wait('@compile') cy.findByRole('button', { name: 'Recompile' }) cy.wait('@compile-pdf') }) it('runs a compile when the Recompile button is pressed', function () { const scope = mockScope() cy.mount(
) // 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' }) cy.contains('Your Paper') }) it('runs a compile on `pdf:recompile` event', function () { const scope = mockScope() cy.mount(
) // 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') 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 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') const scope = mockScope() cy.mount(
).then(() => { cy.findByRole('button', { name: 'Compiling…' }) cy.window().then(win => { win.dispatchEvent(new CustomEvent('pdf:recompile')) }) compileResolve() cy.findByRole('button', { name: 'Recompile' }) cy.contains('Your Paper').should(() => { expect(counter).to.equal(1) }) }) }) it('disables compile button while compile is running', function () { const scope = mockScope() cy.mount(
) cy.findByRole('button', { name: 'Compiling…' }).should('be.disabled') cy.findByRole('button', { name: 'Recompile' }).should('not.be.disabled') }) it('runs a compile on doc change if autocompile is enabled', function () { const scope = mockScope() cy.mount(
) // 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() // switch on auto compile storeAndFireEvent(win, 'autocompile_enabled:project123', true) // fire a doc:changed event => compile win.dispatchEvent(new CustomEvent('doc:changed')) cy.tick(6000) // > AUTO_COMPILE_DEBOUNCE cy.clock().invoke('restore') }) cy.findByRole('button', { name: 'Compiling…' }) cy.findByRole('button', { name: 'Recompile' }) }) it('does not run a compile on doc change if autocompile is disabled', function () { const scope = mockScope() cy.mount(
) // wait for "compile on load" to finish cy.findByRole('button', { name: 'Compiling…' }) cy.findByRole('button', { name: 'Recompile' }) cy.window().then(win => { cy.clock() // make sure auto compile is switched off storeAndFireEvent(win, 'autocompile_enabled:project123', false) // fire a doc:changed event => no compile win.dispatchEvent(new CustomEvent('doc:changed')) cy.tick(5000) // AUTO_COMPILE_DEBOUNCE cy.clock().invoke('restore') }) cy.findByRole('button', { name: 'Recompile' }) }) it('does not run a compile on doc change if autocompile is blocked by syntax check', function () { const scope = mockScope() // enable linting in the editor scope.settings.syntaxValidation = true // mock a linting error scope.hasLintingError = true cy.mount(
) // wait for "compile on load" to finish cy.findByRole('button', { name: 'Compiling…' }) cy.findByRole('button', { name: 'Recompile' }) cy.window().then(win => { cy.clock() // switch on auto compile storeAndFireEvent(win, 'autocompile_enabled:project123', true) // switch on syntax checking storeAndFireEvent(win, 'stop_on_validation_error', true) // fire a doc:changed event => no compile win.dispatchEvent(new CustomEvent('doc:changed')) cy.tick(5000) // AUTO_COMPILE_DEBOUNCE cy.clock().invoke('restore') }) cy.findByRole('button', { name: 'Recompile' }) cy.findByText('Code check failed') }) describe('displays 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.', 'clsi-maintenance': 'The compile servers are down for maintenance, and will be back shortly.', 'compile-in-progress': 'A previous compile is still running. Please wait a minute and try compiling again.', exited: 'Server Error', failure: 'No PDF', generic: 'Server Error', 'project-too-large': 'Project too large', 'rate-limited': 'Compile rate limit hit', terminated: 'Compilation cancelled', timedout: 'Timed out', 'too-recently-compiled': 'This project was compiled very recently, so this compile has been skipped.', unavailable: 'Sorry, the compile server for your project was temporarily unavailable. Please try again in a few moments.', foo: 'Sorry, something went wrong and your project could not be compiled. Please try again in a few moments.', } for (const [status, message] of Object.entries(compileErrorStatuses)) { it(`displays error message for '${status}' status`, function () { cy.intercept('POST', '/project/*/compile?*', { body: { status, clsiServerId: 'foo', compileGroup: 'priority', }, }).as('compile') const scope = mockScope() cy.mount(
) // wait for "compile on load" to finish cy.findByRole('button', { name: 'Compiling…' }) cy.findByRole('button', { name: 'Recompile' }) cy.findByText(message) }) } it('displays expandable raw logs', function () { const scope = mockScope() cy.mount(
) // wait for "compile on load" to finish cy.findByRole('button', { name: 'Compiling…' }) cy.findByRole('button', { name: 'Recompile' }) cy.findByRole('button', { name: 'View logs' }).click() cy.findByRole('button', { name: 'View PDF' }) cy.findByRole('button', { name: 'Expand' }).click() cy.findByRole('button', { name: 'Collapse' }).click() }) 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.intercept('POST', '/project/*/compile?*', { body: { status: 'validation-problems', validationProblems, clsiServerId: 'foo', compileGroup: 'priority', }, }).as('compile') const scope = mockScope() cy.mount(
) // wait for "compile on load" to finish cy.findByRole('button', { name: 'Compiling…' }) cy.findByRole('button', { name: 'Recompile' }) cy.wait('@compile') cy.findByText('Project too large') cy.findByText('Unknown main document') cy.findByText('Conflicting Paths Found') }) it('sends a clear cache request when the button is pressed', function () { const scope = mockScope() cy.mount(
) // wait for "compile on load" to finish cy.findByRole('button', { name: 'Compiling…' }) cy.findByRole('button', { name: 'Recompile' }) cy.findByRole('button', { name: 'View logs' }).click() cy.findByRole('button', { name: 'Clear cached files' }).should( 'not.be.disabled' ) cy.intercept('DELETE', 'project/*/output?*', { statusCode: 204, delay: 100, }).as('clear-cache') // click the button cy.findByRole('button', { name: 'Clear cached files' }).click() cy.findByRole('button', { name: 'Clear cached files' }).should( 'be.disabled' ) cy.wait('@clear-cache') cy.findByRole('button', { name: 'Clear cached files' }).should( 'not.be.disabled' ) }) it('handle "recompile from scratch"', function () { const scope = mockScope() cy.mount(
) // wait for "compile on load" to finish cy.findByRole('button', { name: 'Compiling…' }) cy.findByRole('button', { name: 'Recompile' }) // show the logs UI cy.findByRole('button', { name: 'View logs' }).click() cy.findByRole('button', { name: 'Clear cached files' }).should( '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', hidden: true, }).trigger('click', { force: true }) cy.findByRole('button', { name: 'Clear cached files' }).should( 'be.disabled' ) cy.findByRole('button', { name: 'Compiling…' }) cy.wait('@clear-cache') cy.findByRole('button', { name: 'Recompile' }) cy.wait('@compile') cy.wait('@compile-pdf') }) it('shows an error for an invalid URL', function () { cy.intercept('/build/*/output.pdf?*', { statusCode: 500, body: { message: 'something awful happened', code: 'AWFUL_ERROR', }, }).as('compile-pdf-error') const scope = mockScope() cy.mount(
) cy.wait('@compile-pdf-error') cy.findByText('Something went wrong while rendering this PDF.') cy.findByLabelText('Page 1').should('not.exist') }) it('shows an error for a corrupt PDF', function () { cy.intercept('/build/*/output.pdf?*', { fixture: 'build/output-corrupt.pdf,null', }).as('compile-pdf-corrupt') const scope = mockScope() cy.mount(
) cy.wait('@compile-pdf-corrupt') cy.findByText('Something went wrong while rendering this PDF.') cy.findByLabelText('Page 1').should('not.exist') }) }) describe('human readable logs', function () { it('shows human readable hint for undefined reference errors', function () { cy.intercept('/build/*/output.log?*', { fixture: 'build/output-human-readable.log', }).as('log') const scope = mockScope() cy.mount(
) cy.wait('@log') cy.findByRole('button', { name: 'View logs' }).click() cy.findByText( "Reference `intorduction' on page 1 undefined on input line 11." ) cy.findByText( "Reference `section1' on page 1 undefined on input line 13." ) cy.findByText('There were undefined references.') cy.findAllByText( /You have referenced something which has not yet been labelled/ ).should('have.length', 3) }) it('does not show human readable hint when no undefined reference errors', function () { cy.intercept('/build/*/output.log?*', { fixture: 'build/output-undefined-references.log', }).as('log') const scope = mockScope() cy.mount(
) cy.wait('@log') cy.findByRole('button', { name: 'View logs' }).click() cy.findByText( "Package rerunfilecheck Warning: File `output.brf' has changed. Rerun to get bibliographical references right." ) cy.findByText( /You have referenced something which has not yet been labelled/ ).should('not.exist') }) }) })