mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Migrate worker tests to Cypress (#7359)
GitOrigin-RevId: f373f4215e5f25d14256008cf5f6582eb3124431
This commit is contained in:
parent
69a2283984
commit
5e9af2c15c
48 changed files with 1812 additions and 2052 deletions
726
package-lock.json
generated
726
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -104,7 +104,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Cypress specific rules
|
// Cypress specific rules
|
||||||
"files": ["cypress/**/*.js", "**/test/frontend/**/*.spec.js"],
|
"files": ["cypress/**/*.{js,ts,tsx}", "**/test/frontend/**/*.spec.{js,ts,tsx}"],
|
||||||
"extends": [
|
"extends": [
|
||||||
"plugin:cypress/recommended"
|
"plugin:cypress/recommended"
|
||||||
]
|
]
|
||||||
|
|
2
services/web/.gitignore
vendored
2
services/web/.gitignore
vendored
|
@ -91,3 +91,5 @@ cypress/downloads/
|
||||||
|
|
||||||
# Ace themes for conversion
|
# Ace themes for conversion
|
||||||
modules/source-editor/frontend/js/themes/ace/
|
modules/source-editor/frontend/js/themes/ace/
|
||||||
|
|
||||||
|
!**/fixtures/**/*.log
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
{
|
{
|
||||||
"component": {
|
"component": {
|
||||||
"componentFolder": ".",
|
"componentFolder": ".",
|
||||||
"testFiles": "./{test,modules/**/test}/frontend/components/**/*.spec.js",
|
"testFiles": "./{test,modules/**/test}/frontend/components/**/*.spec.{js,ts,tsx}",
|
||||||
"supportFile": "cypress/support/ct/index.js"
|
"supportFile": "cypress/support/ct/index.ts"
|
||||||
},
|
},
|
||||||
"experimentalFetchPolyfill": true,
|
"fixturesFolder": "cypress/fixtures",
|
||||||
"fixturesFolder": false,
|
|
||||||
"video": false,
|
"video": false,
|
||||||
"viewportHeight": 800,
|
"viewportHeight": 800,
|
||||||
"viewportWidth": 800
|
"viewportWidth": 800
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
log This is pdfTeX, Version 3.14159265-2.6-1.40.21 (TeX Live 2020) (preloaded format=pdflatex 2020.9.10) 8 FEB 2022 16:27
|
||||||
|
entering extended mode
|
||||||
|
\write18 enabled.
|
||||||
|
%&-line parsing enabled.
|
||||||
|
**main.tex
|
||||||
|
(./main.tex
|
||||||
|
LaTeX2e <2020-02-02> patch level 5
|
||||||
|
|
||||||
|
LaTeX Warning: Reference `intorduction' on page 1 undefined on input line 11.
|
||||||
|
|
||||||
|
|
||||||
|
LaTeX Warning: Reference `section1' on page 1 undefined on input line 13.
|
||||||
|
|
||||||
|
[1
|
||||||
|
|
||||||
|
{/usr/local/texlive/2020/texmf-var/fonts/map/pdftex/updmap/pdftex.map}] (/compi
|
||||||
|
le/output.aux)
|
||||||
|
|
||||||
|
LaTeX Warning: There were undefined references.
|
||||||
|
|
||||||
|
)
|
|
@ -0,0 +1,10 @@
|
||||||
|
Package rerunfilecheck Info: File `output.out' has not changed.
|
||||||
|
(rerunfilecheck) Checksum: 339DB29951BB30436898BC39909EA4FA;11265.
|
||||||
|
|
||||||
|
Package rerunfilecheck Warning: File `output.brf' has changed.
|
||||||
|
(rerunfilecheck) Rerun to get bibliographical references right.
|
||||||
|
|
||||||
|
Package rerunfilecheck Info: Checksums for `output.brf':
|
||||||
|
(rerunfilecheck) Before: D41D8CD98F00B204E9800998ECF8427E;0
|
||||||
|
(rerunfilecheck) After: DF3260FAD3828D54C5E4E9337E97F7AF;4841.
|
||||||
|
)
|
1
services/web/cypress/fixtures/build/output.blg
Normal file
1
services/web/cypress/fixtures/build/output.blg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
This is BibTeX, Version 4.0
|
19
services/web/cypress/fixtures/build/output.log
Normal file
19
services/web/cypress/fixtures/build/output.log
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
The LaTeX compiler output
|
||||||
|
* With a lot of details
|
||||||
|
|
||||||
|
Wrapped in an HTML <pre> element with
|
||||||
|
preformatted text which is to be presented exactly
|
||||||
|
as written in the HTML file
|
||||||
|
|
||||||
|
(whitespace included™)
|
||||||
|
|
||||||
|
The text is typically rendered using a non-proportional ("monospace") font.
|
||||||
|
|
||||||
|
LaTeX Font Info: External font `cmex10' loaded for size
|
||||||
|
(Font) <7> on input line 18.
|
||||||
|
LaTeX Font Info: External font `cmex10' loaded for size
|
||||||
|
(Font) <5> on input line 18.
|
||||||
|
! Undefined control sequence.
|
||||||
|
<recently read> \Zlpha
|
||||||
|
|
||||||
|
main.tex, line 23
|
|
@ -3,17 +3,40 @@ module.exports = (on, config) => {
|
||||||
const { startDevServer } = require('@cypress/webpack-dev-server')
|
const { startDevServer } = require('@cypress/webpack-dev-server')
|
||||||
const { merge } = require('webpack-merge')
|
const { merge } = require('webpack-merge')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
|
const webpack = require('webpack')
|
||||||
const devConfig = require('../../webpack.config.dev')
|
const devConfig = require('../../webpack.config.dev')
|
||||||
|
|
||||||
const webpackConfig = merge(devConfig, {
|
const webpackConfig = merge(devConfig, {
|
||||||
devServer: {
|
devServer: {
|
||||||
static: path.join(__dirname, '../../../../public'),
|
static: path.join(__dirname, '../../public'),
|
||||||
},
|
},
|
||||||
stats: 'none',
|
stats: 'none',
|
||||||
|
plugins: [
|
||||||
|
new webpack.EnvironmentPlugin({
|
||||||
|
CYPRESS: true,
|
||||||
|
}),
|
||||||
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
delete webpackConfig.devServer.client
|
delete webpackConfig.devServer.client
|
||||||
|
|
||||||
|
webpackConfig.entry = {}
|
||||||
|
const addWorker = (name, importPath) => {
|
||||||
|
webpackConfig.entry[name] = require.resolve(importPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// add entrypoint under '/' for latex-linter worker
|
||||||
|
addWorker(
|
||||||
|
'latex-linter-worker',
|
||||||
|
'../../modules/source-editor/frontend/js/languages/latex/linter/latex-linter.worker.js'
|
||||||
|
)
|
||||||
|
|
||||||
|
// add entrypoints under '/' for pdfjs workers
|
||||||
|
const pdfjsVersions = ['pdfjs-dist210', 'pdfjs-dist213']
|
||||||
|
for (const name of pdfjsVersions) {
|
||||||
|
addWorker(name, `${name}/legacy/build/pdf.worker.js`)
|
||||||
|
}
|
||||||
|
|
||||||
on('dev-server:start', options => {
|
on('dev-server:start', options => {
|
||||||
return startDevServer({ options, webpackConfig })
|
return startDevServer({ options, webpackConfig })
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
require('../../../frontend/stylesheets/style.less')
|
|
||||||
require('../shared/exceptions')
|
|
||||||
require('./i18n')
|
|
5
services/web/cypress/support/ct/index.ts
Normal file
5
services/web/cypress/support/ct/index.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import '../../../frontend/stylesheets/style.less'
|
||||||
|
import './window' // needs to be before i18n
|
||||||
|
import '../../../frontend/js/i18n'
|
||||||
|
import '../shared/commands'
|
||||||
|
import '../shared/exceptions'
|
|
@ -1,4 +1,2 @@
|
||||||
window.i18n = { currentLangCode: 'en' }
|
window.i18n = { currentLangCode: 'en' }
|
||||||
window.ExposedSettings = { appName: 'Overleaf' }
|
window.ExposedSettings = { appName: 'Overleaf' }
|
||||||
|
|
||||||
require('../../../frontend/js/i18n')
|
|
3
services/web/cypress/support/shared/commands.ts
Normal file
3
services/web/cypress/support/shared/commands.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
import '@testing-library/cypress/add-commands'
|
||||||
|
import './compile'
|
||||||
|
import './events'
|
68
services/web/cypress/support/shared/compile.ts
Normal file
68
services/web/cypress/support/shared/compile.ts
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
import { v4 as uuid } from 'uuid'
|
||||||
|
|
||||||
|
const outputFiles = () => {
|
||||||
|
const build = uuid()
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
path: 'output.pdf',
|
||||||
|
build,
|
||||||
|
url: `/build/${build}/output.pdf`,
|
||||||
|
type: 'pdf',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'output.bbl',
|
||||||
|
build,
|
||||||
|
url: `/build/${build}/output.bbl`,
|
||||||
|
type: 'bbl',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'output.bib',
|
||||||
|
build,
|
||||||
|
url: `/build/${build}/output.bib`,
|
||||||
|
type: 'bib',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'example.txt',
|
||||||
|
build,
|
||||||
|
url: `/build/${build}/example.txt`,
|
||||||
|
type: 'txt',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'output.log',
|
||||||
|
build,
|
||||||
|
url: `/build/${build}/output.log`,
|
||||||
|
type: 'log',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'output.blg',
|
||||||
|
build,
|
||||||
|
url: `/build/${build}/output.blg`,
|
||||||
|
type: 'blg',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
Cypress.Commands.add('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}`)
|
||||||
|
|
||||||
|
cy.intercept('/build/*/output.pdf*', {
|
||||||
|
fixture: 'build/output.pdf,null',
|
||||||
|
}).as(`${prefix}-pdf`)
|
||||||
|
|
||||||
|
cy.intercept('/build/*/output.log*', {
|
||||||
|
fixture: 'build/output.log',
|
||||||
|
}).as(`${prefix}-log`)
|
||||||
|
|
||||||
|
cy.intercept('/build/*/output.blg*', {
|
||||||
|
fixture: 'build/output.blg',
|
||||||
|
}).as(`${prefix}-blg`)
|
||||||
|
})
|
5
services/web/cypress/support/shared/events.ts
Normal file
5
services/web/cypress/support/shared/events.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
Cypress.Commands.add('interceptEvents', () => {
|
||||||
|
cy.intercept('POST', '/event/*', {
|
||||||
|
statusCode: 204,
|
||||||
|
})
|
||||||
|
})
|
8
services/web/cypress/support/shared/types.d.ts
vendored
Normal file
8
services/web/cypress/support/shared/types.d.ts
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
declare namespace Cypress {
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
interface Chainable {
|
||||||
|
interceptCompile(prefix?: string): void
|
||||||
|
interceptEvents(): void
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,7 +25,7 @@ export default function BetaBadge({ tooltip, url = '/beta/participate' }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
BetaBadge.propTypes = {
|
BetaBadge.propTypes = {
|
||||||
tooltip: PropTypes.exact({
|
tooltip: PropTypes.shape({
|
||||||
id: PropTypes.string.isRequired,
|
id: PropTypes.string.isRequired,
|
||||||
text: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired,
|
text: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired,
|
||||||
placement: PropTypes.string,
|
placement: PropTypes.string,
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
export const createWorker = callback => {
|
export const createWorker = callback => {
|
||||||
|
if (process.env.CYPRESS) {
|
||||||
|
return callback()
|
||||||
|
}
|
||||||
const webpackPublicPath = __webpack_public_path__
|
const webpackPublicPath = __webpack_public_path__
|
||||||
__webpack_public_path__ = '/'
|
__webpack_public_path__ = '/'
|
||||||
callback()
|
callback()
|
||||||
|
|
|
@ -116,6 +116,7 @@
|
||||||
"daterangepicker": "https://github.com/overleaf/daterangepicker/archive/e496d2d44ca53e208c930e4cb4bcf29bcefa4550.tar.gz",
|
"daterangepicker": "https://github.com/overleaf/daterangepicker/archive/e496d2d44ca53e208c930e4cb4bcf29bcefa4550.tar.gz",
|
||||||
"downshift": "^6.1.0",
|
"downshift": "^6.1.0",
|
||||||
"east": "^2.0.2",
|
"east": "^2.0.2",
|
||||||
|
"events": "^3.3.0",
|
||||||
"express": "4.17.1",
|
"express": "4.17.1",
|
||||||
"express-bearer-token": "^2.4.0",
|
"express-bearer-token": "^2.4.0",
|
||||||
"express-http-proxy": "^1.6.0",
|
"express-http-proxy": "^1.6.0",
|
||||||
|
@ -218,6 +219,7 @@
|
||||||
"@testing-library/react": "^11.2.7",
|
"@testing-library/react": "^11.2.7",
|
||||||
"@testing-library/react-hooks": "^7.0.0",
|
"@testing-library/react-hooks": "^7.0.0",
|
||||||
"@types/chai": "^4.3.0",
|
"@types/chai": "^4.3.0",
|
||||||
|
"@types/events": "^3.0.0",
|
||||||
"@types/mocha": "^9.1.0",
|
"@types/mocha": "^9.1.0",
|
||||||
"@types/react": "^17.0.40",
|
"@types/react": "^17.0.40",
|
||||||
"@types/react-bootstrap": "^0.32.29",
|
"@types/react-bootstrap": "^0.32.29",
|
||||||
|
@ -264,6 +266,7 @@
|
||||||
"fetch-mock": "^9.10.2",
|
"fetch-mock": "^9.10.2",
|
||||||
"glob": "^7.1.6",
|
"glob": "^7.1.6",
|
||||||
"handlebars-loader": "^1.7.1",
|
"handlebars-loader": "^1.7.1",
|
||||||
|
"html-webpack-plugin": "^5.5.0",
|
||||||
"i18next-scanner": "^3.0.0",
|
"i18next-scanner": "^3.0.0",
|
||||||
"jsdom": "^19.0.0",
|
"jsdom": "^19.0.0",
|
||||||
"jsdom-global": "^3.0.2",
|
"jsdom-global": "^3.0.2",
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
import { mount } from '@cypress/react'
|
||||||
|
import sysendTestHelper from '../../helpers/sysend'
|
||||||
|
import { EditorProviders } from '../../helpers/editor-providers'
|
||||||
|
import DetachCompileButton from '../../../../frontend/js/features/pdf-preview/components/detach-compile-button'
|
||||||
|
import { mockScope } from './scope'
|
||||||
|
|
||||||
|
describe('<DetachCompileButton/>', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
cy.interceptCompile()
|
||||||
|
cy.interceptEvents()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(function () {
|
||||||
|
window.metaAttributesCache = new Map()
|
||||||
|
sysendTestHelper.resetHistory()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('detacher mode and not linked: does not show button ', function () {
|
||||||
|
cy.window().then(win => {
|
||||||
|
win.metaAttributesCache = new Map([['ol-detachRole', 'detacher']])
|
||||||
|
})
|
||||||
|
|
||||||
|
const scope = mockScope()
|
||||||
|
|
||||||
|
mount(
|
||||||
|
<EditorProviders scope={scope}>
|
||||||
|
<DetachCompileButton />
|
||||||
|
</EditorProviders>
|
||||||
|
)
|
||||||
|
|
||||||
|
cy.findByRole('button', { name: 'Recompile' }).should('not.exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('detacher mode and linked: show button ', function () {
|
||||||
|
cy.window().then(win => {
|
||||||
|
win.metaAttributesCache = new Map([['ol-detachRole', 'detacher']])
|
||||||
|
})
|
||||||
|
|
||||||
|
const scope = mockScope()
|
||||||
|
|
||||||
|
mount(
|
||||||
|
<EditorProviders scope={scope}>
|
||||||
|
<DetachCompileButton />
|
||||||
|
</EditorProviders>
|
||||||
|
).then(() => {
|
||||||
|
sysendTestHelper.receiveMessage({
|
||||||
|
role: 'detached',
|
||||||
|
event: 'connected',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.findByRole('button', { name: 'Recompile' })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('not detacher mode and linked: does not show button ', function () {
|
||||||
|
cy.window().then(win => {
|
||||||
|
win.metaAttributesCache = new Map([['ol-detachRole', 'detached']])
|
||||||
|
})
|
||||||
|
|
||||||
|
const scope = mockScope()
|
||||||
|
|
||||||
|
mount(
|
||||||
|
<EditorProviders scope={scope}>
|
||||||
|
<DetachCompileButton />
|
||||||
|
</EditorProviders>
|
||||||
|
).then(() => {
|
||||||
|
sysendTestHelper.receiveMessage({
|
||||||
|
role: 'detacher',
|
||||||
|
event: 'connected',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.findByRole('button', { name: 'Recompile' }).should('not.exist')
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,74 @@
|
||||||
|
import { mount, unmount } from '@cypress/react'
|
||||||
|
import { EditorProviders } from '../../helpers/editor-providers'
|
||||||
|
import PdfJsViewer from '../../../../frontend/js/features/pdf-preview/components/pdf-js-viewer'
|
||||||
|
import { mockScope } from './scope'
|
||||||
|
|
||||||
|
describe('<PdfJSViewer/>', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
cy.interceptCompile()
|
||||||
|
cy.interceptEvents()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('loads all PDF pages', function () {
|
||||||
|
const scope = mockScope()
|
||||||
|
|
||||||
|
mount(
|
||||||
|
<EditorProviders scope={scope}>
|
||||||
|
<div className="pdf-viewer">
|
||||||
|
<PdfJsViewer url="/build/123/output.pdf" />
|
||||||
|
</div>
|
||||||
|
</EditorProviders>
|
||||||
|
)
|
||||||
|
|
||||||
|
cy.findByLabelText('Page 1')
|
||||||
|
cy.findByLabelText('Page 2')
|
||||||
|
cy.findByLabelText('Page 3')
|
||||||
|
cy.findByLabelText('Page 4').should('not.exist')
|
||||||
|
|
||||||
|
cy.contains('Your Paper')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders pages in a "loading" state', function () {
|
||||||
|
const scope = mockScope()
|
||||||
|
|
||||||
|
mount(
|
||||||
|
<EditorProviders scope={scope}>
|
||||||
|
<div className="pdf-viewer">
|
||||||
|
<PdfJsViewer url="/build/123/output.pdf" />
|
||||||
|
</div>
|
||||||
|
</EditorProviders>
|
||||||
|
)
|
||||||
|
|
||||||
|
cy.findByLabelText('Loading…')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('can be unmounted while loading a document', function () {
|
||||||
|
const scope = mockScope()
|
||||||
|
|
||||||
|
mount(
|
||||||
|
<EditorProviders scope={scope}>
|
||||||
|
<div className="pdf-viewer">
|
||||||
|
<PdfJsViewer url="/build/123/output.pdf" />
|
||||||
|
</div>
|
||||||
|
</EditorProviders>
|
||||||
|
)
|
||||||
|
|
||||||
|
unmount()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('can be unmounted after loading a document', function () {
|
||||||
|
const scope = mockScope()
|
||||||
|
|
||||||
|
mount(
|
||||||
|
<EditorProviders scope={scope}>
|
||||||
|
<div className="pdf-viewer">
|
||||||
|
<PdfJsViewer url="/build/123/output.pdf" />
|
||||||
|
</div>
|
||||||
|
</EditorProviders>
|
||||||
|
)
|
||||||
|
|
||||||
|
cy.findByLabelText('Page 1')
|
||||||
|
|
||||||
|
unmount()
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,143 @@
|
||||||
|
import { mount } from '@cypress/react'
|
||||||
|
import sysendTestHelper from '../../helpers/sysend'
|
||||||
|
import { EditorProviders } from '../../helpers/editor-providers'
|
||||||
|
import PdfLogsEntries from '../../../../frontend/js/features/pdf-preview/components/pdf-logs-entries'
|
||||||
|
window.metaAttributesCache = new Map([['ol-debugPdfDetach', true]])
|
||||||
|
|
||||||
|
describe('<PdfLogsEntries/>', function () {
|
||||||
|
const fakeEntity = { type: 'doc' }
|
||||||
|
|
||||||
|
const logEntries = [
|
||||||
|
{
|
||||||
|
file: 'main.tex',
|
||||||
|
line: 9,
|
||||||
|
column: 8,
|
||||||
|
level: 'error',
|
||||||
|
message: 'LaTeX Error',
|
||||||
|
content: 'See the LaTeX manual',
|
||||||
|
raw: '',
|
||||||
|
ruleId: 'hint_misplaced_alignment_tab_character',
|
||||||
|
key: '',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
let props
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
props = {
|
||||||
|
fileTreeManager: {
|
||||||
|
findEntityByPath: cy.stub().as('findEntityByPath').returns(fakeEntity),
|
||||||
|
},
|
||||||
|
editorManager: {
|
||||||
|
openDoc: cy.stub().as('openDoc'),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cy.interceptCompile()
|
||||||
|
cy.interceptEvents()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(function () {
|
||||||
|
window.metaAttributesCache = new Map()
|
||||||
|
sysendTestHelper.resetHistory()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('displays human readable hint', function () {
|
||||||
|
mount(
|
||||||
|
<EditorProviders {...props}>
|
||||||
|
<PdfLogsEntries entries={logEntries} />
|
||||||
|
</EditorProviders>
|
||||||
|
)
|
||||||
|
|
||||||
|
cy.contains('You have placed an alignment tab character')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('opens doc on click', function () {
|
||||||
|
mount(
|
||||||
|
<EditorProviders {...props}>
|
||||||
|
<PdfLogsEntries entries={logEntries} />
|
||||||
|
</EditorProviders>
|
||||||
|
)
|
||||||
|
|
||||||
|
cy.findByRole('button', {
|
||||||
|
name: 'Navigate to log position in source code: main.tex, 9',
|
||||||
|
})
|
||||||
|
.click()
|
||||||
|
.then(() => {
|
||||||
|
expect(props.fileTreeManager.findEntityByPath).to.be.calledOnce
|
||||||
|
expect(props.editorManager.openDoc).to.be.calledOnce
|
||||||
|
expect(props.editorManager.openDoc).to.be.calledWith(fakeEntity, {
|
||||||
|
gotoLine: 9,
|
||||||
|
gotoColumn: 8,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('opens doc via detached action', function () {
|
||||||
|
cy.window().then(win => {
|
||||||
|
win.metaAttributesCache = new Map([['ol-detachRole', 'detacher']])
|
||||||
|
})
|
||||||
|
|
||||||
|
mount(
|
||||||
|
<EditorProviders {...props}>
|
||||||
|
<PdfLogsEntries entries={logEntries} />
|
||||||
|
</EditorProviders>
|
||||||
|
).then(() => {
|
||||||
|
sysendTestHelper.receiveMessage({
|
||||||
|
role: 'detached',
|
||||||
|
event: 'action-sync-to-entry',
|
||||||
|
data: {
|
||||||
|
args: [
|
||||||
|
{
|
||||||
|
file: 'main.tex',
|
||||||
|
line: 7,
|
||||||
|
column: 6,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(props.fileTreeManager.findEntityByPath).to.be.calledOnce
|
||||||
|
expect(props.editorManager.openDoc).to.be.calledOnce
|
||||||
|
expect(props.editorManager.openDoc).to.be.calledWith(fakeEntity, {
|
||||||
|
gotoLine: 7,
|
||||||
|
gotoColumn: 6,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sends open doc clicks via detached action', function () {
|
||||||
|
cy.window().then(win => {
|
||||||
|
win.metaAttributesCache = new Map([['ol-detachRole', 'detached']])
|
||||||
|
})
|
||||||
|
|
||||||
|
mount(
|
||||||
|
<EditorProviders {...props}>
|
||||||
|
<PdfLogsEntries entries={logEntries} />
|
||||||
|
</EditorProviders>
|
||||||
|
)
|
||||||
|
|
||||||
|
cy.findByRole('button', {
|
||||||
|
name: 'Navigate to log position in source code: main.tex, 9',
|
||||||
|
})
|
||||||
|
.click()
|
||||||
|
.then(() => {
|
||||||
|
expect(props.fileTreeManager.findEntityByPath).not.to.be.called
|
||||||
|
expect(props.editorManager.openDoc).not.to.be.called
|
||||||
|
|
||||||
|
expect(sysendTestHelper.getLastBroacastMessage()).to.deep.equal({
|
||||||
|
role: 'detached',
|
||||||
|
event: 'action-sync-to-entry',
|
||||||
|
data: {
|
||||||
|
args: [
|
||||||
|
{
|
||||||
|
file: 'main.tex',
|
||||||
|
line: 9,
|
||||||
|
column: 8,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,75 @@
|
||||||
|
import { mount } from '@cypress/react'
|
||||||
|
import sysendTestHelper from '../../helpers/sysend'
|
||||||
|
import PdfPreviewDetachedRoot from '../../../../frontend/js/features/pdf-preview/components/pdf-preview-detached-root'
|
||||||
|
|
||||||
|
describe('<PdfPreviewDetachedRoot/>', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
window.user = { id: 'user1' }
|
||||||
|
|
||||||
|
window.metaAttributesCache = new Map<string, unknown>([
|
||||||
|
['ol-user', window.user],
|
||||||
|
['ol-project_id', 'project1'],
|
||||||
|
['ol-detachRole', 'detached'],
|
||||||
|
['ol-projectName', 'Project Name'],
|
||||||
|
])
|
||||||
|
|
||||||
|
cy.interceptCompile()
|
||||||
|
cy.interceptEvents()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(function () {
|
||||||
|
window.metaAttributesCache = new Map()
|
||||||
|
sysendTestHelper.resetHistory()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('syncs compiling state', function () {
|
||||||
|
mount(<PdfPreviewDetachedRoot />).then(() => {
|
||||||
|
sysendTestHelper.receiveMessage({
|
||||||
|
role: 'detacher',
|
||||||
|
event: 'connected',
|
||||||
|
})
|
||||||
|
|
||||||
|
sysendTestHelper.receiveMessage({
|
||||||
|
role: 'detacher',
|
||||||
|
event: 'state-compiling',
|
||||||
|
data: { value: true },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.findByRole('button', { name: 'Compiling…' })
|
||||||
|
cy.findByRole('button', { name: 'Recompile' })
|
||||||
|
.should('not.exist')
|
||||||
|
.then(() => {
|
||||||
|
sysendTestHelper.receiveMessage({
|
||||||
|
role: 'detacher',
|
||||||
|
event: 'state-compiling',
|
||||||
|
data: { value: false },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
cy.findByRole('button', { name: 'Recompile' })
|
||||||
|
cy.findByRole('button', { name: 'Compiling…' }).should('not.exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sends a clear cache request when the button is pressed', function () {
|
||||||
|
mount(<PdfPreviewDetachedRoot />).then(() => {
|
||||||
|
sysendTestHelper.receiveMessage({
|
||||||
|
role: 'detacher',
|
||||||
|
event: 'state-showLogs',
|
||||||
|
data: { value: true },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.findByRole('button', { name: 'Clear cached files' })
|
||||||
|
.should('not.be.disabled')
|
||||||
|
.click()
|
||||||
|
.then(() => {
|
||||||
|
expect(sysendTestHelper.getLastBroacastMessage()).to.deep.equal({
|
||||||
|
role: 'detached',
|
||||||
|
event: 'action-clearCache',
|
||||||
|
data: {
|
||||||
|
args: [],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,102 @@
|
||||||
|
import { mount } from '@cypress/react'
|
||||||
|
import sysendTestHelper from '../../helpers/sysend'
|
||||||
|
import { EditorProviders } from '../../helpers/editor-providers'
|
||||||
|
import PdfPreviewHybridToolbar from '../../../../frontend/js/features/pdf-preview/components/pdf-preview-hybrid-toolbar'
|
||||||
|
|
||||||
|
describe('<PdfPreviewHybridToolbar/>', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
cy.interceptCompile()
|
||||||
|
cy.interceptEvents()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(function () {
|
||||||
|
window.metaAttributesCache = new Map()
|
||||||
|
sysendTestHelper.resetHistory()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows normal mode', function () {
|
||||||
|
mount(
|
||||||
|
<EditorProviders>
|
||||||
|
<PdfPreviewHybridToolbar />
|
||||||
|
</EditorProviders>
|
||||||
|
)
|
||||||
|
|
||||||
|
cy.findByRole('button', { name: 'Recompile' })
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('orphan mode', function () {
|
||||||
|
it('shows connecting message on load', function () {
|
||||||
|
cy.window().then(win => {
|
||||||
|
win.metaAttributesCache = new Map([['ol-detachRole', 'detached']])
|
||||||
|
})
|
||||||
|
|
||||||
|
mount(
|
||||||
|
<EditorProviders>
|
||||||
|
<PdfPreviewHybridToolbar />
|
||||||
|
</EditorProviders>
|
||||||
|
)
|
||||||
|
|
||||||
|
cy.contains('Connecting with the editor')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows compile UI when connected', function () {
|
||||||
|
cy.window().then(win => {
|
||||||
|
win.metaAttributesCache = new Map([['ol-detachRole', 'detached']])
|
||||||
|
})
|
||||||
|
|
||||||
|
mount(
|
||||||
|
<EditorProviders>
|
||||||
|
<PdfPreviewHybridToolbar />
|
||||||
|
</EditorProviders>
|
||||||
|
).then(() => {
|
||||||
|
sysendTestHelper.receiveMessage({
|
||||||
|
role: 'detacher',
|
||||||
|
event: 'connected',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.findByRole('button', { name: 'Recompile' })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows connecting message when disconnected', function () {
|
||||||
|
cy.window().then(win => {
|
||||||
|
win.metaAttributesCache = new Map([['ol-detachRole', 'detached']])
|
||||||
|
})
|
||||||
|
|
||||||
|
mount(
|
||||||
|
<EditorProviders>
|
||||||
|
<PdfPreviewHybridToolbar />
|
||||||
|
</EditorProviders>
|
||||||
|
).then(() => {
|
||||||
|
sysendTestHelper.receiveMessage({
|
||||||
|
role: 'detacher',
|
||||||
|
event: 'connected',
|
||||||
|
})
|
||||||
|
sysendTestHelper.receiveMessage({
|
||||||
|
role: 'detacher',
|
||||||
|
event: 'closed',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.contains('Connecting with the editor')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows redirect button after timeout', function () {
|
||||||
|
cy.window().then(win => {
|
||||||
|
win.metaAttributesCache = new Map([['ol-detachRole', 'detached']])
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.clock()
|
||||||
|
|
||||||
|
mount(
|
||||||
|
<EditorProviders>
|
||||||
|
<PdfPreviewHybridToolbar />
|
||||||
|
</EditorProviders>
|
||||||
|
)
|
||||||
|
|
||||||
|
cy.tick(6000)
|
||||||
|
|
||||||
|
cy.findByRole('button', { name: 'Redirect to editor' })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,599 @@
|
||||||
|
import { mount } from '@cypress/react'
|
||||||
|
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, key, value) => {
|
||||||
|
localStorage.setItem(key, value)
|
||||||
|
win.dispatchEvent(new StorageEvent('storage', { key }))
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('<PdfPreview/>', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
cy.interceptCompile()
|
||||||
|
cy.interceptEvents()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders the PDF preview', function () {
|
||||||
|
const scope = mockScope()
|
||||||
|
|
||||||
|
mount(
|
||||||
|
<EditorProviders scope={scope}>
|
||||||
|
<div className="pdf-viewer">
|
||||||
|
<PdfPreview />
|
||||||
|
</div>
|
||||||
|
</EditorProviders>
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
|
||||||
|
mount(
|
||||||
|
<EditorProviders scope={scope}>
|
||||||
|
<div className="pdf-viewer">
|
||||||
|
<PdfPreview />
|
||||||
|
</div>
|
||||||
|
</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' })
|
||||||
|
|
||||||
|
cy.contains('Your Paper')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('runs a compile on `pdf:recompile` event', function () {
|
||||||
|
const scope = mockScope()
|
||||||
|
|
||||||
|
mount(
|
||||||
|
<EditorProviders scope={scope}>
|
||||||
|
<div className="pdf-viewer">
|
||||||
|
<PdfPreview />
|
||||||
|
</div>
|
||||||
|
</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')
|
||||||
|
|
||||||
|
cy.findByRole('button', { name: 'Recompile' })
|
||||||
|
|
||||||
|
cy.wait('@recompile-pdf')
|
||||||
|
cy.contains('Your Paper')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not compile while compiling', function () {
|
||||||
|
let compileResolve
|
||||||
|
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()
|
||||||
|
|
||||||
|
mount(
|
||||||
|
<EditorProviders scope={scope}>
|
||||||
|
<div className="pdf-viewer">
|
||||||
|
<PdfPreview />
|
||||||
|
</div>
|
||||||
|
</EditorProviders>
|
||||||
|
).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()
|
||||||
|
|
||||||
|
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')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('runs a compile on doc change if autocompile is enabled', function () {
|
||||||
|
const scope = mockScope()
|
||||||
|
|
||||||
|
mount(
|
||||||
|
<EditorProviders scope={scope}>
|
||||||
|
<div className="pdf-viewer">
|
||||||
|
<PdfPreview />
|
||||||
|
</div>
|
||||||
|
</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()
|
||||||
|
|
||||||
|
// switch on auto compile
|
||||||
|
storeAndFireEvent(win, 'autocompile_enabled:project123', true)
|
||||||
|
|
||||||
|
// fire a doc:changed event => compile
|
||||||
|
win.dispatchEvent(new CustomEvent('doc:changed'))
|
||||||
|
|
||||||
|
cy.tick(5000) // 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()
|
||||||
|
|
||||||
|
mount(
|
||||||
|
<EditorProviders scope={scope}>
|
||||||
|
<div className="pdf-viewer">
|
||||||
|
<PdfPreview />
|
||||||
|
</div>
|
||||||
|
</EditorProviders>
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
mount(
|
||||||
|
<EditorProviders scope={scope}>
|
||||||
|
<div className="pdf-viewer">
|
||||||
|
<PdfPreview />
|
||||||
|
</div>
|
||||||
|
</EditorProviders>
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
|
||||||
|
mount(
|
||||||
|
<EditorProviders scope={scope}>
|
||||||
|
<div className="pdf-viewer">
|
||||||
|
<PdfPreview />
|
||||||
|
</div>
|
||||||
|
</EditorProviders>
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
|
||||||
|
mount(
|
||||||
|
<EditorProviders scope={scope}>
|
||||||
|
<div className="pdf-viewer">
|
||||||
|
<PdfPreview />
|
||||||
|
</div>
|
||||||
|
</EditorProviders>
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
|
||||||
|
mount(
|
||||||
|
<EditorProviders scope={scope}>
|
||||||
|
<div className="pdf-viewer">
|
||||||
|
<PdfPreview />
|
||||||
|
</div>
|
||||||
|
</EditorProviders>
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
|
||||||
|
mount(
|
||||||
|
<EditorProviders scope={scope}>
|
||||||
|
<div className="pdf-viewer">
|
||||||
|
<PdfPreview />
|
||||||
|
</div>
|
||||||
|
</EditorProviders>
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
|
||||||
|
mount(
|
||||||
|
<EditorProviders scope={scope}>
|
||||||
|
<div className="pdf-viewer">
|
||||||
|
<PdfPreview />
|
||||||
|
</div>
|
||||||
|
</EditorProviders>
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
|
||||||
|
mount(
|
||||||
|
<EditorProviders scope={scope}>
|
||||||
|
<div className="pdf-viewer">
|
||||||
|
<PdfPreview />
|
||||||
|
</div>
|
||||||
|
</EditorProviders>
|
||||||
|
)
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
mount(
|
||||||
|
<EditorProviders scope={scope}>
|
||||||
|
<div className="pdf-viewer">
|
||||||
|
<PdfPreview />
|
||||||
|
</div>
|
||||||
|
</EditorProviders>
|
||||||
|
)
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
mount(
|
||||||
|
<EditorProviders scope={scope}>
|
||||||
|
<div className="pdf-viewer">
|
||||||
|
<PdfPreview />
|
||||||
|
</div>
|
||||||
|
</EditorProviders>
|
||||||
|
)
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
mount(
|
||||||
|
<EditorProviders scope={scope}>
|
||||||
|
<div className="pdf-viewer">
|
||||||
|
<PdfPreview />
|
||||||
|
</div>
|
||||||
|
</EditorProviders>
|
||||||
|
)
|
||||||
|
|
||||||
|
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')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,380 @@
|
||||||
|
import PdfSynctexControls from '../../../../frontend/js/features/pdf-preview/components/pdf-synctex-controls'
|
||||||
|
import sysendTestHelper from '../../helpers/sysend'
|
||||||
|
import { cloneDeep } from 'lodash'
|
||||||
|
import { useDetachCompileContext as useCompileContext } from '../../../../frontend/js/shared/context/detach-compile-context'
|
||||||
|
import { useFileTreeData } from '../../../../frontend/js/shared/context/file-tree-data-context'
|
||||||
|
import { useEffect } from 'react'
|
||||||
|
import { mount } from '@cypress/react'
|
||||||
|
import { EditorProviders } from '../../helpers/editor-providers'
|
||||||
|
import { mockScope } from './scope'
|
||||||
|
|
||||||
|
const mockHighlights = [
|
||||||
|
{
|
||||||
|
page: 1,
|
||||||
|
h: 85.03936,
|
||||||
|
v: 509.999878,
|
||||||
|
width: 441.921265,
|
||||||
|
height: 8.855677,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
page: 1,
|
||||||
|
h: 85.03936,
|
||||||
|
v: 486.089539,
|
||||||
|
width: 441.921265,
|
||||||
|
height: 8.855677,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const mockPosition = {
|
||||||
|
page: 1,
|
||||||
|
offset: { top: 10, left: 10 },
|
||||||
|
pageSize: { height: 500, width: 500 },
|
||||||
|
}
|
||||||
|
|
||||||
|
const mockSelectedEntities = [{ type: 'doc' }]
|
||||||
|
|
||||||
|
const WithPosition = ({ mockPosition }) => {
|
||||||
|
const { setPosition } = useCompileContext()
|
||||||
|
|
||||||
|
// mock PDF scroll position update
|
||||||
|
useEffect(() => {
|
||||||
|
setPosition(mockPosition)
|
||||||
|
}, [mockPosition, setPosition])
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const WithSelectedEntities = ({ mockSelectedEntities = [] }) => {
|
||||||
|
const { setSelectedEntities } = useFileTreeData()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSelectedEntities(mockSelectedEntities)
|
||||||
|
}, [mockSelectedEntities, setSelectedEntities])
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
describe('<PdfSynctexControls/>', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
window.metaAttributesCache = new Map()
|
||||||
|
|
||||||
|
cy.interceptCompile()
|
||||||
|
cy.interceptEvents()
|
||||||
|
|
||||||
|
cy.intercept('/project/*/sync/code?*', {
|
||||||
|
body: { pdf: cloneDeep(mockHighlights) },
|
||||||
|
delay: 100,
|
||||||
|
}).as('sync-code')
|
||||||
|
|
||||||
|
cy.intercept('/project/*/sync/pdf?*', {
|
||||||
|
body: { code: [{ file: 'main.tex', line: 100 }] },
|
||||||
|
delay: 100,
|
||||||
|
}).as('sync-pdf')
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(function () {
|
||||||
|
window.metaAttributesCache = new Map()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('handles clicks on sync buttons', function () {
|
||||||
|
const scope = mockScope()
|
||||||
|
|
||||||
|
mount(
|
||||||
|
<EditorProviders scope={scope}>
|
||||||
|
<WithPosition mockPosition={mockPosition} />
|
||||||
|
<WithSelectedEntities mockSelectedEntities={mockSelectedEntities} />
|
||||||
|
<PdfSynctexControls />
|
||||||
|
</EditorProviders>
|
||||||
|
)
|
||||||
|
|
||||||
|
cy.get('.synctex-control-icon').should('have.length', 2)
|
||||||
|
|
||||||
|
// mock editor cursor position update
|
||||||
|
cy.window().then(win => {
|
||||||
|
win.dispatchEvent(
|
||||||
|
new CustomEvent('cursor:editor:update', {
|
||||||
|
detail: { row: 100, column: 10 },
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.get('body')
|
||||||
|
.findByRole('button', { name: 'Go to code location in PDF' })
|
||||||
|
.click()
|
||||||
|
cy.get('body')
|
||||||
|
.findByRole('button', { name: 'Go to code location in PDF' })
|
||||||
|
.should('be.disabled')
|
||||||
|
|
||||||
|
cy.wait('@sync-code')
|
||||||
|
|
||||||
|
cy.get('body')
|
||||||
|
.findByRole('button', { name: /^Go to PDF location in code/ })
|
||||||
|
.click()
|
||||||
|
cy.get('body')
|
||||||
|
.findByRole('button', { name: /^Go to PDF location in code/ })
|
||||||
|
.should('be.disabled')
|
||||||
|
|
||||||
|
cy.wait('@sync-pdf')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('disables button when multiple entities are selected', function () {
|
||||||
|
const scope = mockScope()
|
||||||
|
|
||||||
|
mount(
|
||||||
|
<EditorProviders scope={scope}>
|
||||||
|
<WithPosition mockPosition={mockPosition} />
|
||||||
|
<WithSelectedEntities
|
||||||
|
mockSelectedEntities={[{ type: 'doc' }, { type: 'doc' }]}
|
||||||
|
/>
|
||||||
|
<PdfSynctexControls />
|
||||||
|
</EditorProviders>
|
||||||
|
)
|
||||||
|
|
||||||
|
cy.get('body')
|
||||||
|
.findByRole('button', { name: 'Go to code location in PDF' })
|
||||||
|
.should('be.disabled')
|
||||||
|
|
||||||
|
cy.get('body')
|
||||||
|
.findByRole('button', { name: /^Go to PDF location in code/ })
|
||||||
|
.should('be.disabled')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('disables button when a file is selected', function () {
|
||||||
|
const scope = mockScope()
|
||||||
|
|
||||||
|
mount(
|
||||||
|
<EditorProviders scope={scope}>
|
||||||
|
<WithPosition mockPosition={mockPosition} />
|
||||||
|
<WithSelectedEntities mockSelectedEntities={[{ type: 'file' }]} />
|
||||||
|
<PdfSynctexControls />
|
||||||
|
</EditorProviders>
|
||||||
|
)
|
||||||
|
|
||||||
|
cy.get('body')
|
||||||
|
.findByRole('button', { name: 'Go to code location in PDF' })
|
||||||
|
.should('be.disabled')
|
||||||
|
|
||||||
|
cy.get('body')
|
||||||
|
.findByRole('button', { name: /^Go to PDF location in code/ })
|
||||||
|
.should('be.disabled')
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('with detacher role', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
window.metaAttributesCache.set('ol-detachRole', 'detacher')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not have go to PDF location button nor arrow icon', function () {
|
||||||
|
const scope = mockScope()
|
||||||
|
|
||||||
|
mount(
|
||||||
|
<EditorProviders scope={scope}>
|
||||||
|
<WithPosition mockPosition={mockPosition} />
|
||||||
|
<WithSelectedEntities mockSelectedEntities={mockSelectedEntities} />
|
||||||
|
<PdfSynctexControls />
|
||||||
|
</EditorProviders>
|
||||||
|
)
|
||||||
|
|
||||||
|
cy.get('body')
|
||||||
|
.findByRole('button', { name: /^Go to PDF location in code/ })
|
||||||
|
.should('not.exist')
|
||||||
|
|
||||||
|
cy.get('.synctex-control-icon').should('not.exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('send set highlights action', function () {
|
||||||
|
const scope = mockScope()
|
||||||
|
|
||||||
|
mount(
|
||||||
|
<EditorProviders scope={scope}>
|
||||||
|
<WithPosition mockPosition={mockPosition} />
|
||||||
|
<WithSelectedEntities mockSelectedEntities={mockSelectedEntities} />
|
||||||
|
<PdfSynctexControls />
|
||||||
|
</EditorProviders>
|
||||||
|
).then(() => {
|
||||||
|
sysendTestHelper.resetHistory()
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.wait('@compile')
|
||||||
|
|
||||||
|
// mock editor cursor position update
|
||||||
|
cy.window().then(win => {
|
||||||
|
win.dispatchEvent(
|
||||||
|
new CustomEvent('cursor:editor:update', {
|
||||||
|
detail: { row: 100, column: 10 },
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.findByRole('button', {
|
||||||
|
name: 'Go to code location in PDF',
|
||||||
|
})
|
||||||
|
.should('not.be.disabled')
|
||||||
|
.click()
|
||||||
|
|
||||||
|
cy.findByRole('button', {
|
||||||
|
name: 'Go to code location in PDF',
|
||||||
|
}).should('be.disabled')
|
||||||
|
|
||||||
|
cy.wait('@sync-code').then(() => {
|
||||||
|
// synctex is called locally and the result are broadcast for the detached tab
|
||||||
|
expect(sysendTestHelper.getLastBroacastMessage()).to.deep.equal({
|
||||||
|
role: 'detacher',
|
||||||
|
event: 'action-setHighlights',
|
||||||
|
data: { args: [mockHighlights] },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('reacts to sync to code action', function () {
|
||||||
|
const scope = mockScope()
|
||||||
|
|
||||||
|
mount(
|
||||||
|
<EditorProviders scope={scope}>
|
||||||
|
<WithPosition mockPosition={mockPosition} />
|
||||||
|
<WithSelectedEntities mockSelectedEntities={mockSelectedEntities} />
|
||||||
|
<PdfSynctexControls />
|
||||||
|
</EditorProviders>
|
||||||
|
)
|
||||||
|
|
||||||
|
cy.wait('@compile').then(() => {
|
||||||
|
sysendTestHelper.receiveMessage({
|
||||||
|
role: 'detached',
|
||||||
|
event: 'action-sync-to-code',
|
||||||
|
data: {
|
||||||
|
args: [mockPosition],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.wait('@sync-pdf')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('with detached role', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
window.metaAttributesCache.set('ol-detachRole', 'detached')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not have go to code location button nor arrow icon', function () {
|
||||||
|
const scope = mockScope()
|
||||||
|
|
||||||
|
mount(
|
||||||
|
<EditorProviders scope={scope}>
|
||||||
|
<WithPosition mockPosition={mockPosition} />
|
||||||
|
<PdfSynctexControls />
|
||||||
|
</EditorProviders>
|
||||||
|
)
|
||||||
|
|
||||||
|
cy.findByRole('button', {
|
||||||
|
name: 'Go to code location in PDF',
|
||||||
|
}).should('not.exist')
|
||||||
|
|
||||||
|
cy.get('.synctex-control-icon').should('not.exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
// eslint-disable-next-line mocha/no-skipped-tests
|
||||||
|
it.skip('send go to code line action', function () {
|
||||||
|
const scope = mockScope()
|
||||||
|
|
||||||
|
mount(
|
||||||
|
<EditorProviders scope={scope}>
|
||||||
|
<WithPosition mockPosition={mockPosition} />
|
||||||
|
<PdfSynctexControls />
|
||||||
|
</EditorProviders>
|
||||||
|
)
|
||||||
|
|
||||||
|
cy.wait('@compile')
|
||||||
|
|
||||||
|
cy.get('body')
|
||||||
|
.findByRole('button', { name: /^Go to PDF location in code/ })
|
||||||
|
.should('be.disabled')
|
||||||
|
.then(() => {
|
||||||
|
sysendTestHelper.receiveMessage({
|
||||||
|
role: 'detached',
|
||||||
|
event: 'state-has-single-selected-doc',
|
||||||
|
data: { value: true },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.get('body')
|
||||||
|
.findByRole('button', { name: /^Go to PDF location in code/ })
|
||||||
|
.should('not.be.disabled')
|
||||||
|
.then(() => {
|
||||||
|
sysendTestHelper.resetHistory()
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.get('body')
|
||||||
|
.findByRole('button', { name: /^Go to PDF location in code/ })
|
||||||
|
.click()
|
||||||
|
|
||||||
|
// the button is only disabled when the state is updated via sysend
|
||||||
|
cy.get('body')
|
||||||
|
.findByRole('button', { name: /^Go to PDF location in code/ })
|
||||||
|
.should('not.be.disabled')
|
||||||
|
|
||||||
|
cy.get('.synctex-spin-icon')
|
||||||
|
.should('not.exist')
|
||||||
|
.then(() => {
|
||||||
|
expect(sysendTestHelper.getLastBroacastMessage()).to.deep.equal({
|
||||||
|
role: 'detached',
|
||||||
|
event: 'action-sync-to-code',
|
||||||
|
data: {
|
||||||
|
args: [mockPosition, 72],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// eslint-disable-next-line mocha/no-skipped-tests
|
||||||
|
it.skip('update inflight state', function () {
|
||||||
|
const scope = mockScope()
|
||||||
|
|
||||||
|
mount(
|
||||||
|
<EditorProviders scope={scope}>
|
||||||
|
<WithPosition mockPosition={mockPosition} />
|
||||||
|
<PdfSynctexControls />
|
||||||
|
</EditorProviders>
|
||||||
|
).then(() => {
|
||||||
|
sysendTestHelper.receiveMessage({
|
||||||
|
role: 'detached',
|
||||||
|
event: 'state-has-single-selected-doc',
|
||||||
|
data: { value: true },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.get('body')
|
||||||
|
.findByRole('button', { name: /^Go to PDF location in code/ })
|
||||||
|
.should('be.disabled')
|
||||||
|
|
||||||
|
cy.get('.synctex-spin-icon')
|
||||||
|
.should('not.exist')
|
||||||
|
.then(() => {
|
||||||
|
sysendTestHelper.receiveMessage({
|
||||||
|
role: 'detacher',
|
||||||
|
event: 'state-sync-to-code-inflight',
|
||||||
|
data: { value: true },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.get('body')
|
||||||
|
.findByRole('button', { name: /^Go to PDF location in code/ })
|
||||||
|
.should('be.disabled')
|
||||||
|
|
||||||
|
cy.get('.synctex-spin-icon')
|
||||||
|
.should('have.length', 1)
|
||||||
|
.then(() => {
|
||||||
|
sysendTestHelper.receiveMessage({
|
||||||
|
role: 'detacher',
|
||||||
|
event: 'state-sync-to-code-inflight',
|
||||||
|
data: { value: false },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.get('body')
|
||||||
|
.findByRole('button', { name: /^Go to PDF location in code/ })
|
||||||
|
.should('not.be.disabled')
|
||||||
|
|
||||||
|
cy.get('.synctex-spin-icon').should('not.exist')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
13
services/web/test/frontend/components/pdf-preview/scope.tsx
Normal file
13
services/web/test/frontend/components/pdf-preview/scope.tsx
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
export const mockScope = () => ({
|
||||||
|
settings: {
|
||||||
|
syntaxValidation: false,
|
||||||
|
pdfViewer: 'pdfjs',
|
||||||
|
},
|
||||||
|
editor: {
|
||||||
|
sharejs_doc: {
|
||||||
|
doc_id: 'test-doc',
|
||||||
|
getSnapshot: () => 'some doc content',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
hasLintingError: false,
|
||||||
|
})
|
|
@ -9,13 +9,8 @@ import FileTreeContextMenu from '../../../../../../frontend/js/features/file-tre
|
||||||
describe('<FileTreeitemInner />', function () {
|
describe('<FileTreeitemInner />', function () {
|
||||||
const setContextMenuCoords = sinon.stub()
|
const setContextMenuCoords = sinon.stub()
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
global.requestAnimationFrame = sinon.stub()
|
|
||||||
})
|
|
||||||
|
|
||||||
afterEach(function () {
|
afterEach(function () {
|
||||||
setContextMenuCoords.reset()
|
setContextMenuCoords.reset()
|
||||||
delete global.requestAnimationFrame
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('menu', function () {
|
describe('menu', function () {
|
||||||
|
|
|
@ -14,13 +14,11 @@ describe('<FileTreeRoot/>', function () {
|
||||||
const onInit = sinon.stub()
|
const onInit = sinon.stub()
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
global.requestAnimationFrame = sinon.stub()
|
|
||||||
window.metaAttributesCache = new Map()
|
window.metaAttributesCache = new Map()
|
||||||
window.metaAttributesCache.set('ol-user', { id: 'user1' })
|
window.metaAttributesCache.set('ol-user', { id: 'user1' })
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(function () {
|
afterEach(function () {
|
||||||
delete global.requestAnimationFrame
|
|
||||||
fetchMock.restore()
|
fetchMock.restore()
|
||||||
onSelect.reset()
|
onSelect.reset()
|
||||||
onInit.reset()
|
onInit.reset()
|
||||||
|
|
|
@ -15,13 +15,11 @@ describe('FileTree Create Folder Flow', function () {
|
||||||
const onInit = sinon.stub()
|
const onInit = sinon.stub()
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
global.requestAnimationFrame = sinon.stub()
|
|
||||||
window.metaAttributesCache = new Map()
|
window.metaAttributesCache = new Map()
|
||||||
window.metaAttributesCache.set('ol-user', { id: 'user1' })
|
window.metaAttributesCache.set('ol-user', { id: 'user1' })
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(function () {
|
afterEach(function () {
|
||||||
delete global.requestAnimationFrame
|
|
||||||
fetchMock.restore()
|
fetchMock.restore()
|
||||||
onSelect.reset()
|
onSelect.reset()
|
||||||
onInit.reset()
|
onInit.reset()
|
||||||
|
|
|
@ -15,13 +15,11 @@ describe('FileTree Rename Entity Flow', function () {
|
||||||
const onInit = sinon.stub()
|
const onInit = sinon.stub()
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
global.requestAnimationFrame = sinon.stub()
|
|
||||||
window.metaAttributesCache = new Map()
|
window.metaAttributesCache = new Map()
|
||||||
window.metaAttributesCache.set('ol-user', { id: 'user1' })
|
window.metaAttributesCache.set('ol-user', { id: 'user1' })
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(function () {
|
afterEach(function () {
|
||||||
delete global.requestAnimationFrame
|
|
||||||
fetchMock.restore()
|
fetchMock.restore()
|
||||||
onSelect.reset()
|
onSelect.reset()
|
||||||
onInit.reset()
|
onInit.reset()
|
||||||
|
|
|
@ -1,51 +0,0 @@
|
||||||
import DetachCompileButton from '../../../../../frontend/js/features/pdf-preview/components/detach-compile-button'
|
|
||||||
import { renderWithEditorContext } from '../../../helpers/render-with-context'
|
|
||||||
import { screen } from '@testing-library/react'
|
|
||||||
import sysendTestHelper from '../../../helpers/sysend'
|
|
||||||
import { expect } from 'chai'
|
|
||||||
|
|
||||||
describe('<DetachCompileButton/>', function () {
|
|
||||||
afterEach(function () {
|
|
||||||
window.metaAttributesCache = new Map()
|
|
||||||
sysendTestHelper.resetHistory()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('detacher mode and linked: show button ', async function () {
|
|
||||||
window.metaAttributesCache.set('ol-detachRole', 'detacher')
|
|
||||||
renderWithEditorContext(<DetachCompileButton />)
|
|
||||||
sysendTestHelper.receiveMessage({
|
|
||||||
role: 'detached',
|
|
||||||
event: 'connected',
|
|
||||||
})
|
|
||||||
|
|
||||||
await screen.getByRole('button', {
|
|
||||||
name: 'Recompile',
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('detacher mode and not linked: does not show button ', async function () {
|
|
||||||
window.metaAttributesCache.set('ol-detachRole', 'detacher')
|
|
||||||
renderWithEditorContext(<DetachCompileButton />)
|
|
||||||
|
|
||||||
expect(
|
|
||||||
await screen.queryByRole('button', {
|
|
||||||
name: 'Recompile',
|
|
||||||
})
|
|
||||||
).to.not.exist
|
|
||||||
})
|
|
||||||
|
|
||||||
it('not detacher mode and linked: does not show button ', async function () {
|
|
||||||
window.metaAttributesCache.set('ol-detachRole', 'detached')
|
|
||||||
renderWithEditorContext(<DetachCompileButton />)
|
|
||||||
sysendTestHelper.receiveMessage({
|
|
||||||
role: 'detacher',
|
|
||||||
event: 'connected',
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(
|
|
||||||
await screen.queryByRole('button', {
|
|
||||||
name: 'Recompile',
|
|
||||||
})
|
|
||||||
).to.not.exist
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,37 +0,0 @@
|
||||||
import { expect } from 'chai'
|
|
||||||
import { screen } from '@testing-library/react'
|
|
||||||
import path from 'path'
|
|
||||||
import { renderWithEditorContext } from '../../../helpers/render-with-context'
|
|
||||||
import { pathToFileURL } from 'url'
|
|
||||||
import PdfJsViewer from '../../../../../frontend/js/features/pdf-preview/components/pdf-js-viewer'
|
|
||||||
|
|
||||||
const example = pathToFileURL(
|
|
||||||
path.join(__dirname, '../fixtures/test-example.pdf')
|
|
||||||
).toString()
|
|
||||||
|
|
||||||
describe('<PdfJSViewer/>', function () {
|
|
||||||
it('loads all PDF pages', async function () {
|
|
||||||
renderWithEditorContext(<PdfJsViewer url={example} />)
|
|
||||||
|
|
||||||
await screen.findByLabelText('Page 1')
|
|
||||||
await screen.findByLabelText('Page 2')
|
|
||||||
await screen.findByLabelText('Page 3')
|
|
||||||
expect(screen.queryByLabelText('Page 4')).to.not.exist
|
|
||||||
})
|
|
||||||
|
|
||||||
it('renders pages in a "loading" state', async function () {
|
|
||||||
renderWithEditorContext(<PdfJsViewer url={example} />)
|
|
||||||
await screen.findByLabelText('Loading…')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('can be unmounted while loading a document', async function () {
|
|
||||||
const { unmount } = renderWithEditorContext(<PdfJsViewer url={example} />)
|
|
||||||
unmount()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('can be unmounted after loading a document', async function () {
|
|
||||||
const { unmount } = renderWithEditorContext(<PdfJsViewer url={example} />)
|
|
||||||
await screen.findByLabelText('Page 1')
|
|
||||||
unmount()
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,120 +0,0 @@
|
||||||
import PdfLogsEntries from '../../../../../frontend/js/features/pdf-preview/components/pdf-logs-entries'
|
|
||||||
import { renderWithEditorContext } from '../../../helpers/render-with-context'
|
|
||||||
import { screen, fireEvent } from '@testing-library/react'
|
|
||||||
import sysendTestHelper from '../../../helpers/sysend'
|
|
||||||
import { expect } from 'chai'
|
|
||||||
import sinon from 'sinon'
|
|
||||||
|
|
||||||
describe('<PdfLogsEntries/>', function () {
|
|
||||||
const fileTreeManager = {}
|
|
||||||
const editorManager = {}
|
|
||||||
const logEntries = [
|
|
||||||
{
|
|
||||||
file: 'main.tex',
|
|
||||||
line: 9,
|
|
||||||
column: 8,
|
|
||||||
level: 'error',
|
|
||||||
message: 'LaTeX Error',
|
|
||||||
content: 'See the LaTeX manual',
|
|
||||||
raw: '',
|
|
||||||
ruleId: 'hint_misplaced_alignment_tab_character',
|
|
||||||
key: '',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
const fakeEntity = { type: 'doc' }
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
fileTreeManager.findEntityByPath = sinon.stub().returns(fakeEntity)
|
|
||||||
editorManager.openDoc = sinon.stub()
|
|
||||||
})
|
|
||||||
|
|
||||||
afterEach(function () {
|
|
||||||
window.metaAttributesCache = new Map()
|
|
||||||
sysendTestHelper.resetHistory()
|
|
||||||
fileTreeManager.findEntityByPath.resetHistory()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('displays human readable hint', async function () {
|
|
||||||
renderWithEditorContext(<PdfLogsEntries entries={logEntries} />, {
|
|
||||||
fileTreeManager,
|
|
||||||
editorManager,
|
|
||||||
})
|
|
||||||
screen.getByText(/You have placed an alignment tab character/)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('opens doc on click', async function () {
|
|
||||||
renderWithEditorContext(<PdfLogsEntries entries={logEntries} />, {
|
|
||||||
fileTreeManager,
|
|
||||||
editorManager,
|
|
||||||
})
|
|
||||||
|
|
||||||
const button = await screen.getByRole('button', {
|
|
||||||
name: 'Navigate to log position in source code: main.tex, 9',
|
|
||||||
})
|
|
||||||
fireEvent.click(button)
|
|
||||||
sinon.assert.calledOnce(fileTreeManager.findEntityByPath)
|
|
||||||
sinon.assert.calledOnce(editorManager.openDoc)
|
|
||||||
sinon.assert.calledWith(editorManager.openDoc, fakeEntity, {
|
|
||||||
gotoLine: 9,
|
|
||||||
gotoColumn: 8,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('opens doc via detached action', async function () {
|
|
||||||
window.metaAttributesCache.set('ol-detachRole', 'detacher')
|
|
||||||
|
|
||||||
renderWithEditorContext(<PdfLogsEntries entries={logEntries} />, {
|
|
||||||
fileTreeManager,
|
|
||||||
editorManager,
|
|
||||||
})
|
|
||||||
|
|
||||||
sysendTestHelper.receiveMessage({
|
|
||||||
role: 'detached',
|
|
||||||
event: 'action-sync-to-entry',
|
|
||||||
data: {
|
|
||||||
args: [
|
|
||||||
{
|
|
||||||
file: 'main.tex',
|
|
||||||
line: 7,
|
|
||||||
column: 6,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
sinon.assert.calledOnce(fileTreeManager.findEntityByPath)
|
|
||||||
sinon.assert.calledOnce(editorManager.openDoc)
|
|
||||||
sinon.assert.calledWith(editorManager.openDoc, fakeEntity, {
|
|
||||||
gotoLine: 7,
|
|
||||||
gotoColumn: 6,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('sends open doc clicks via detached action', async function () {
|
|
||||||
window.metaAttributesCache.set('ol-detachRole', 'detached')
|
|
||||||
renderWithEditorContext(<PdfLogsEntries entries={logEntries} />, {
|
|
||||||
fileTreeManager,
|
|
||||||
editorManager,
|
|
||||||
})
|
|
||||||
|
|
||||||
const button = await screen.getByRole('button', {
|
|
||||||
name: 'Navigate to log position in source code: main.tex, 9',
|
|
||||||
})
|
|
||||||
fireEvent.click(button)
|
|
||||||
sinon.assert.notCalled(fileTreeManager.findEntityByPath)
|
|
||||||
sinon.assert.notCalled(editorManager.openDoc)
|
|
||||||
expect(sysendTestHelper.getLastBroacastMessage()).to.deep.equal({
|
|
||||||
role: 'detached',
|
|
||||||
event: 'action-sync-to-entry',
|
|
||||||
data: {
|
|
||||||
args: [
|
|
||||||
{
|
|
||||||
file: 'main.tex',
|
|
||||||
line: 9,
|
|
||||||
column: 8,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,70 +0,0 @@
|
||||||
import { expect } from 'chai'
|
|
||||||
import { render, screen, fireEvent } from '@testing-library/react'
|
|
||||||
import sysendTestHelper from '../../../helpers/sysend'
|
|
||||||
import PdfPreviewDetachedRoot from '../../../../../frontend/js/features/pdf-preview/components/pdf-preview-detached-root'
|
|
||||||
|
|
||||||
describe('<PdfPreviewDetachedRoot/>', function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
const user = { id: 'user1' }
|
|
||||||
window.user = user
|
|
||||||
|
|
||||||
window.metaAttributesCache = new Map()
|
|
||||||
window.metaAttributesCache.set('ol-user', user)
|
|
||||||
window.metaAttributesCache.set('ol-project_id', 'project1')
|
|
||||||
window.metaAttributesCache.set('ol-detachRole', 'detached')
|
|
||||||
window.metaAttributesCache.set('ol-projectName', 'Project Name')
|
|
||||||
})
|
|
||||||
|
|
||||||
afterEach(function () {
|
|
||||||
window.metaAttributesCache = new Map()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('syncs compiling state', async function () {
|
|
||||||
render(<PdfPreviewDetachedRoot />)
|
|
||||||
|
|
||||||
sysendTestHelper.receiveMessage({
|
|
||||||
role: 'detacher',
|
|
||||||
event: 'connected',
|
|
||||||
})
|
|
||||||
|
|
||||||
sysendTestHelper.receiveMessage({
|
|
||||||
role: 'detacher',
|
|
||||||
event: 'state-compiling',
|
|
||||||
data: { value: true },
|
|
||||||
})
|
|
||||||
await screen.findByRole('button', { name: 'Compiling…' })
|
|
||||||
expect(screen.queryByRole('button', { name: 'Recompile' })).to.not.exist
|
|
||||||
|
|
||||||
sysendTestHelper.receiveMessage({
|
|
||||||
role: 'detacher',
|
|
||||||
event: 'state-compiling',
|
|
||||||
data: { value: false },
|
|
||||||
})
|
|
||||||
await screen.findByRole('button', { name: 'Recompile' })
|
|
||||||
expect(screen.queryByRole('button', { name: 'Compiling…' })).to.not.exist
|
|
||||||
})
|
|
||||||
|
|
||||||
it('sends a clear cache request when the button is pressed', async function () {
|
|
||||||
render(<PdfPreviewDetachedRoot />)
|
|
||||||
|
|
||||||
sysendTestHelper.receiveMessage({
|
|
||||||
role: 'detacher',
|
|
||||||
event: 'state-showLogs',
|
|
||||||
data: { value: true },
|
|
||||||
})
|
|
||||||
|
|
||||||
const clearCacheButton = await screen.findByRole('button', {
|
|
||||||
name: 'Clear cached files',
|
|
||||||
})
|
|
||||||
expect(clearCacheButton.hasAttribute('disabled')).to.be.false
|
|
||||||
|
|
||||||
fireEvent.click(clearCacheButton)
|
|
||||||
expect(sysendTestHelper.getLastBroacastMessage()).to.deep.equal({
|
|
||||||
role: 'detached',
|
|
||||||
event: 'action-clearCache',
|
|
||||||
data: {
|
|
||||||
args: [],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,74 +0,0 @@
|
||||||
import sinon from 'sinon'
|
|
||||||
import PdfPreviewHybridToolbar from '../../../../../frontend/js/features/pdf-preview/components/pdf-preview-hybrid-toolbar'
|
|
||||||
import { renderWithEditorContext } from '../../../helpers/render-with-context'
|
|
||||||
import { screen } from '@testing-library/react'
|
|
||||||
import sysendTestHelper from '../../../helpers/sysend'
|
|
||||||
|
|
||||||
describe('<PdfPreviewHybridToolbar/>', function () {
|
|
||||||
let clock
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
clock = sinon.useFakeTimers()
|
|
||||||
})
|
|
||||||
|
|
||||||
afterEach(function () {
|
|
||||||
window.metaAttributesCache = new Map()
|
|
||||||
sysendTestHelper.resetHistory()
|
|
||||||
clock.runAll()
|
|
||||||
clock.restore()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('shows normal mode', async function () {
|
|
||||||
renderWithEditorContext(<PdfPreviewHybridToolbar />)
|
|
||||||
|
|
||||||
await screen.getByRole('button', {
|
|
||||||
name: 'Recompile',
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('orphan mode', async function () {
|
|
||||||
it('shows connecting message on load', async function () {
|
|
||||||
window.metaAttributesCache.set('ol-detachRole', 'detached')
|
|
||||||
renderWithEditorContext(<PdfPreviewHybridToolbar />)
|
|
||||||
|
|
||||||
await screen.getByText(/Connecting with the editor/)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('shows compile UI when connected', async function () {
|
|
||||||
window.metaAttributesCache.set('ol-detachRole', 'detached')
|
|
||||||
renderWithEditorContext(<PdfPreviewHybridToolbar />)
|
|
||||||
sysendTestHelper.receiveMessage({
|
|
||||||
role: 'detacher',
|
|
||||||
event: 'connected',
|
|
||||||
})
|
|
||||||
await screen.getByRole('button', {
|
|
||||||
name: 'Recompile',
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('shows connecting message when disconnected', async function () {
|
|
||||||
window.metaAttributesCache.set('ol-detachRole', 'detached')
|
|
||||||
renderWithEditorContext(<PdfPreviewHybridToolbar />)
|
|
||||||
sysendTestHelper.receiveMessage({
|
|
||||||
role: 'detacher',
|
|
||||||
event: 'connected',
|
|
||||||
})
|
|
||||||
sysendTestHelper.receiveMessage({
|
|
||||||
role: 'detacher',
|
|
||||||
event: 'closed',
|
|
||||||
})
|
|
||||||
|
|
||||||
await screen.getByText(/Connecting with the editor/)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('shows redirect button after timeout', async function () {
|
|
||||||
window.metaAttributesCache.set('ol-detachRole', 'detached')
|
|
||||||
renderWithEditorContext(<PdfPreviewHybridToolbar />)
|
|
||||||
clock.tick(6000)
|
|
||||||
|
|
||||||
await screen.getByRole('button', {
|
|
||||||
name: 'Redirect to editor',
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,523 +0,0 @@
|
||||||
import { expect } from 'chai'
|
|
||||||
import sinon from 'sinon'
|
|
||||||
import fetchMock from 'fetch-mock'
|
|
||||||
import { screen, fireEvent, waitFor, cleanup } from '@testing-library/react'
|
|
||||||
import PdfPreview from '../../../../../frontend/js/features/pdf-preview/components/pdf-preview'
|
|
||||||
import { renderWithEditorContext } from '../../../helpers/render-with-context'
|
|
||||||
import nock from 'nock'
|
|
||||||
import {
|
|
||||||
corruptPDF,
|
|
||||||
defaultFileResponses,
|
|
||||||
mockBuildFile,
|
|
||||||
mockClearCache,
|
|
||||||
mockCompile,
|
|
||||||
mockCompileError,
|
|
||||||
mockValidationProblems,
|
|
||||||
mockValidPdf,
|
|
||||||
} from '../utils/mock-compile'
|
|
||||||
|
|
||||||
const mockDelayed = fn => {
|
|
||||||
let _resolve = null
|
|
||||||
const delayPromise = new Promise((resolve, reject) => {
|
|
||||||
_resolve = resolve
|
|
||||||
})
|
|
||||||
fn(delayPromise)
|
|
||||||
return _resolve
|
|
||||||
}
|
|
||||||
|
|
||||||
const storeAndFireEvent = (key, value) => {
|
|
||||||
localStorage.setItem(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 () {
|
|
||||||
let clock
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
clock = sinon.useFakeTimers({
|
|
||||||
shouldAdvanceTime: true,
|
|
||||||
now: Date.now(),
|
|
||||||
})
|
|
||||||
nock.cleanAll()
|
|
||||||
})
|
|
||||||
|
|
||||||
afterEach(function () {
|
|
||||||
clock.runAll()
|
|
||||||
clock.restore()
|
|
||||||
fetchMock.reset()
|
|
||||||
localStorage.clear()
|
|
||||||
sinon.restore()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('renders the PDF preview', async function () {
|
|
||||||
mockCompile()
|
|
||||||
mockBuildFile()
|
|
||||||
mockValidPdf()
|
|
||||||
|
|
||||||
renderWithEditorContext(<PdfPreview />, { scope })
|
|
||||||
|
|
||||||
// wait for "compile on load" to finish
|
|
||||||
await screen.findByRole('button', { name: 'Compiling…' })
|
|
||||||
await screen.findByRole('button', { name: 'Recompile' })
|
|
||||||
})
|
|
||||||
|
|
||||||
it('runs a compile when the Recompile button is pressed', async function () {
|
|
||||||
mockCompile()
|
|
||||||
mockBuildFile()
|
|
||||||
mockValidPdf()
|
|
||||||
|
|
||||||
renderWithEditorContext(<PdfPreview />, { scope })
|
|
||||||
|
|
||||||
// wait for "compile on load" to finish
|
|
||||||
await screen.findByRole('button', { name: 'Compiling…' })
|
|
||||||
await screen.findByRole('button', { name: 'Recompile' })
|
|
||||||
|
|
||||||
mockValidPdf()
|
|
||||||
|
|
||||||
// press the Recompile button => compile
|
|
||||||
const button = screen.getByRole('button', { name: 'Recompile' })
|
|
||||||
button.click()
|
|
||||||
await screen.findByRole('button', { name: 'Compiling…' })
|
|
||||||
await screen.findByRole('button', { name: 'Recompile' })
|
|
||||||
|
|
||||||
expect(fetchMock.calls()).to.have.length(6)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('runs a compile on `pdf:recompile` event', async function () {
|
|
||||||
mockCompile()
|
|
||||||
mockBuildFile()
|
|
||||||
mockValidPdf()
|
|
||||||
|
|
||||||
renderWithEditorContext(<PdfPreview />, { scope })
|
|
||||||
|
|
||||||
// wait for "compile on load" to finish
|
|
||||||
await screen.findByRole('button', { name: 'Compiling…' })
|
|
||||||
await screen.findByRole('button', { name: 'Recompile' })
|
|
||||||
|
|
||||||
mockValidPdf()
|
|
||||||
|
|
||||||
fireEvent(window, new CustomEvent('pdf:recompile'))
|
|
||||||
|
|
||||||
await screen.findByRole('button', { name: 'Compiling…' })
|
|
||||||
await screen.findByRole('button', { name: 'Recompile' })
|
|
||||||
|
|
||||||
expect(fetchMock.calls()).to.have.length(6)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('does not compile while compiling', async function () {
|
|
||||||
mockDelayed(mockCompile)
|
|
||||||
|
|
||||||
renderWithEditorContext(<PdfPreview />, { scope })
|
|
||||||
|
|
||||||
// trigger compiles while "compile on load" is running
|
|
||||||
await screen.findByRole('button', { name: 'Compiling…' })
|
|
||||||
fireEvent(window, new CustomEvent('pdf:recompile'))
|
|
||||||
|
|
||||||
expect(fetchMock.calls()).to.have.length(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('disables compile button while compile is running', async function () {
|
|
||||||
mockCompile()
|
|
||||||
mockBuildFile()
|
|
||||||
mockValidPdf()
|
|
||||||
|
|
||||||
renderWithEditorContext(<PdfPreview />, { scope })
|
|
||||||
|
|
||||||
let button = screen.getByRole('button', { name: 'Compiling…' })
|
|
||||||
expect(button.hasAttribute('disabled')).to.be.true
|
|
||||||
|
|
||||||
button = await screen.findByRole('button', { name: 'Recompile' })
|
|
||||||
expect(button.hasAttribute('disabled')).to.be.false
|
|
||||||
})
|
|
||||||
|
|
||||||
it('runs a compile on doc change if autocompile is enabled', async function () {
|
|
||||||
mockCompile()
|
|
||||||
mockBuildFile()
|
|
||||||
mockValidPdf()
|
|
||||||
|
|
||||||
renderWithEditorContext(<PdfPreview />, { scope })
|
|
||||||
|
|
||||||
// wait for "compile on load" to finish
|
|
||||||
await screen.findByRole('button', { name: 'Compiling…' })
|
|
||||||
await screen.findByRole('button', { name: 'Recompile' })
|
|
||||||
|
|
||||||
// switch on auto compile
|
|
||||||
storeAndFireEvent('autocompile_enabled:project123', true)
|
|
||||||
|
|
||||||
mockValidPdf()
|
|
||||||
|
|
||||||
// fire a doc:changed event => compile
|
|
||||||
fireEvent(window, new CustomEvent('doc:changed'))
|
|
||||||
clock.tick(2000) // AUTO_COMPILE_DEBOUNCE
|
|
||||||
|
|
||||||
await screen.findByRole('button', { name: 'Compiling…' })
|
|
||||||
await screen.findByRole('button', { name: 'Recompile' })
|
|
||||||
|
|
||||||
expect(fetchMock.calls()).to.have.length(6)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('does not run a compile on doc change if autocompile is disabled', async function () {
|
|
||||||
mockCompile()
|
|
||||||
mockBuildFile()
|
|
||||||
mockValidPdf()
|
|
||||||
|
|
||||||
renderWithEditorContext(<PdfPreview />, { scope })
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
|
|
||||||
// fire a doc:changed event => no compile
|
|
||||||
fireEvent(window, new CustomEvent('doc:changed'))
|
|
||||||
clock.tick(2000) // AUTO_COMPILE_DEBOUNCE
|
|
||||||
screen.getByRole('button', { name: 'Recompile' })
|
|
||||||
|
|
||||||
expect(fetchMock.calls()).to.have.length(3)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('does not run a compile on doc change if autocompile is blocked by syntax check', async function () {
|
|
||||||
mockCompile()
|
|
||||||
mockBuildFile()
|
|
||||||
mockValidPdf()
|
|
||||||
|
|
||||||
renderWithEditorContext(<PdfPreview />, {
|
|
||||||
scope: {
|
|
||||||
...scope,
|
|
||||||
'settings.syntaxValidation': true, // enable linting in the editor
|
|
||||||
hasLintingError: true, // mock a linting error
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
|
|
||||||
// fire a doc:changed event => no compile
|
|
||||||
fireEvent(window, new CustomEvent('doc:changed'))
|
|
||||||
clock.tick(2000) // AUTO_COMPILE_DEBOUNCE
|
|
||||||
screen.getByRole('button', { name: 'Recompile' })
|
|
||||||
await screen.findByText('Code check failed')
|
|
||||||
|
|
||||||
expect(fetchMock.calls()).to.have.length(3)
|
|
||||||
})
|
|
||||||
|
|
||||||
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`, async function () {
|
|
||||||
cleanup()
|
|
||||||
fetchMock.restore()
|
|
||||||
mockCompileError(status)
|
|
||||||
|
|
||||||
renderWithEditorContext(<PdfPreview />, { scope })
|
|
||||||
|
|
||||||
// wait for "compile on load" to finish
|
|
||||||
await screen.findByRole('button', { name: 'Compiling…' })
|
|
||||||
await screen.findByRole('button', { name: 'Recompile' })
|
|
||||||
|
|
||||||
screen.getByText(message)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
it('displays expandable raw logs', async function () {
|
|
||||||
mockCompile()
|
|
||||||
mockBuildFile()
|
|
||||||
mockValidPdf()
|
|
||||||
|
|
||||||
// pretend that the content is large enough to trigger a "collapse"
|
|
||||||
// (in jsdom these values are always zero)
|
|
||||||
sinon.stub(HTMLElement.prototype, 'scrollHeight').value(500)
|
|
||||||
sinon.stub(HTMLElement.prototype, 'scrollWidth').value(500)
|
|
||||||
|
|
||||||
renderWithEditorContext(<PdfPreview />, { scope })
|
|
||||||
|
|
||||||
// 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: 'View logs' })
|
|
||||||
logsButton.click()
|
|
||||||
|
|
||||||
await screen.findByRole('button', { name: 'View PDF' })
|
|
||||||
|
|
||||||
// expand the log
|
|
||||||
const [expandButton] = screen.getAllByRole('button', { name: 'Expand' })
|
|
||||||
expandButton.click()
|
|
||||||
|
|
||||||
// collapse the log
|
|
||||||
const [collapseButton] = screen.getAllByRole('button', { name: 'Collapse' })
|
|
||||||
collapseButton.click()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('displays error messages if there were validation problems', async function () {
|
|
||||||
const validationProblems = {
|
|
||||||
sizeCheck: {
|
|
||||||
resources: [
|
|
||||||
{ path: 'foo/bar', kbSize: 76221 },
|
|
||||||
{ path: 'bar/baz', kbSize: 2342 },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
mainFile: true,
|
|
||||||
conflictedPaths: [
|
|
||||||
{
|
|
||||||
path: 'foo/bar',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'foo/baz',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
mockValidationProblems(validationProblems)
|
|
||||||
|
|
||||||
renderWithEditorContext(<PdfPreview />, { scope })
|
|
||||||
|
|
||||||
// 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')
|
|
||||||
|
|
||||||
expect(fetchMock.called('express:/project/:projectId/compile')).to.be.true // TODO: auto_compile query param
|
|
||||||
expect(fetchMock.called('begin:https://clsi.test-overleaf.com/')).to.be
|
|
||||||
.false // TODO: actual path
|
|
||||||
})
|
|
||||||
|
|
||||||
it('sends a clear cache request when the button is pressed', async function () {
|
|
||||||
mockCompile()
|
|
||||||
mockBuildFile()
|
|
||||||
mockValidPdf()
|
|
||||||
|
|
||||||
renderWithEditorContext(<PdfPreview />, { scope })
|
|
||||||
|
|
||||||
// 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: 'View logs',
|
|
||||||
})
|
|
||||||
logsButton.click()
|
|
||||||
|
|
||||||
const clearCacheButton = await screen.findByRole('button', {
|
|
||||||
name: 'Clear cached files',
|
|
||||||
})
|
|
||||||
expect(clearCacheButton.hasAttribute('disabled')).to.be.false
|
|
||||||
|
|
||||||
mockClearCache()
|
|
||||||
|
|
||||||
// click the button
|
|
||||||
clearCacheButton.click()
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(clearCacheButton.hasAttribute('disabled')).to.be.true
|
|
||||||
})
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(clearCacheButton.hasAttribute('disabled')).to.be.false
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(fetchMock.called('express:/project/:projectId/compile')).to.be.true // TODO: auto_compile query param
|
|
||||||
expect(fetchMock.called('begin:https://clsi.test-overleaf.com/')).to.be.true // TODO: actual path
|
|
||||||
})
|
|
||||||
|
|
||||||
it('handle "recompile from scratch"', async function () {
|
|
||||||
mockCompile()
|
|
||||||
mockBuildFile()
|
|
||||||
mockValidPdf()
|
|
||||||
|
|
||||||
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: 'View logs',
|
|
||||||
})
|
|
||||||
logsButton.click()
|
|
||||||
|
|
||||||
const clearCacheButton = await screen.findByRole('button', {
|
|
||||||
name: 'Clear cached files',
|
|
||||||
})
|
|
||||||
expect(clearCacheButton.hasAttribute('disabled')).to.be.false
|
|
||||||
|
|
||||||
mockValidPdf()
|
|
||||||
const finishClearCache = mockDelayed(mockClearCache)
|
|
||||||
|
|
||||||
const recompileFromScratch = screen.getByRole('menuitem', {
|
|
||||||
name: 'Recompile from scratch',
|
|
||||||
hidden: true,
|
|
||||||
})
|
|
||||||
recompileFromScratch.click()
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(clearCacheButton.hasAttribute('disabled')).to.be.true
|
|
||||||
})
|
|
||||||
|
|
||||||
finishClearCache()
|
|
||||||
|
|
||||||
// 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('begin:https://clsi.test-overleaf.com/')).to.be.true // TODO: actual path
|
|
||||||
})
|
|
||||||
|
|
||||||
it('shows an error for an invalid URL', async function () {
|
|
||||||
mockCompile()
|
|
||||||
mockBuildFile()
|
|
||||||
|
|
||||||
nock('https://clsi.test-overleaf.com')
|
|
||||||
.get(/^\/build\/output.pdf/)
|
|
||||||
.replyWithError({
|
|
||||||
message: 'something awful happened',
|
|
||||||
code: 'AWFUL_ERROR',
|
|
||||||
})
|
|
||||||
|
|
||||||
renderWithEditorContext(<PdfPreview />, { scope })
|
|
||||||
|
|
||||||
await screen.findByText('Something went wrong while rendering this PDF.')
|
|
||||||
expect(screen.queryByLabelText('Page 1')).to.not.exist
|
|
||||||
|
|
||||||
expect(nock.isDone()).to.be.true
|
|
||||||
})
|
|
||||||
|
|
||||||
it('shows an error for a corrupt PDF', async function () {
|
|
||||||
mockCompile()
|
|
||||||
mockBuildFile()
|
|
||||||
|
|
||||||
nock('https://clsi.test-overleaf.com')
|
|
||||||
.get(/^\/build\/output.pdf/)
|
|
||||||
.replyWithFile(200, corruptPDF)
|
|
||||||
|
|
||||||
renderWithEditorContext(<PdfPreview />, { scope })
|
|
||||||
|
|
||||||
await screen.findByText('Something went wrong while rendering this PDF.')
|
|
||||||
expect(screen.queryByLabelText('Page 1')).to.not.exist
|
|
||||||
|
|
||||||
expect(nock.isDone()).to.be.true
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('human readable logs', function () {
|
|
||||||
it('shows human readable hint for undefined reference errors', async function () {
|
|
||||||
mockCompile()
|
|
||||||
mockBuildFile({
|
|
||||||
...defaultFileResponses,
|
|
||||||
'/build/output.log': `
|
|
||||||
log This is pdfTeX, Version 3.14159265-2.6-1.40.21 (TeX Live 2020) (preloaded format=pdflatex 2020.9.10) 8 FEB 2022 16:27
|
|
||||||
entering extended mode
|
|
||||||
\\write18 enabled.
|
|
||||||
%&-line parsing enabled.
|
|
||||||
**main.tex
|
|
||||||
(./main.tex
|
|
||||||
LaTeX2e <2020-02-02> patch level 5
|
|
||||||
|
|
||||||
LaTeX Warning: Reference \`intorduction' on page 1 undefined on input line 11.
|
|
||||||
|
|
||||||
|
|
||||||
LaTeX Warning: Reference \`section1' on page 1 undefined on input line 13.
|
|
||||||
|
|
||||||
[1
|
|
||||||
|
|
||||||
{/usr/local/texlive/2020/texmf-var/fonts/map/pdftex/updmap/pdftex.map}] (/compi
|
|
||||||
le/output.aux)
|
|
||||||
|
|
||||||
LaTeX Warning: There were undefined references.
|
|
||||||
|
|
||||||
)
|
|
||||||
`,
|
|
||||||
})
|
|
||||||
mockValidPdf()
|
|
||||||
|
|
||||||
renderWithEditorContext(<PdfPreview />, { scope })
|
|
||||||
|
|
||||||
await screen.findByText(
|
|
||||||
"Reference `intorduction' on page 1 undefined on input line 11."
|
|
||||||
)
|
|
||||||
await screen.findByText(
|
|
||||||
"Reference `section1' on page 1 undefined on input line 13."
|
|
||||||
)
|
|
||||||
await screen.findByText('There were undefined references.')
|
|
||||||
const hints = await screen.findAllByText(
|
|
||||||
/You have referenced something which has not yet been labelled/
|
|
||||||
)
|
|
||||||
expect(hints.length).to.equal(3)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('idoes not show human readable hint for undefined reference errors', async function () {
|
|
||||||
mockCompile()
|
|
||||||
mockBuildFile({
|
|
||||||
...defaultFileResponses,
|
|
||||||
'/build/output.log': `
|
|
||||||
Package rerunfilecheck Info: File \`output.out' has not changed.
|
|
||||||
(rerunfilecheck) Checksum: 339DB29951BB30436898BC39909EA4FA;11265.
|
|
||||||
|
|
||||||
Package rerunfilecheck Warning: File \`output.brf' has changed.
|
|
||||||
(rerunfilecheck) Rerun to get bibliographical references right.
|
|
||||||
|
|
||||||
Package rerunfilecheck Info: Checksums for \`output.brf':
|
|
||||||
(rerunfilecheck) Before: D41D8CD98F00B204E9800998ECF8427E;0
|
|
||||||
(rerunfilecheck) After: DF3260FAD3828D54C5E4E9337E97F7AF;4841.
|
|
||||||
)
|
|
||||||
`,
|
|
||||||
})
|
|
||||||
mockValidPdf()
|
|
||||||
|
|
||||||
renderWithEditorContext(<PdfPreview />, { scope })
|
|
||||||
|
|
||||||
await screen.findByText(
|
|
||||||
/Package rerunfilecheck Warning: File `output.brf' has changed. Rerun to get bibliographical references right./
|
|
||||||
)
|
|
||||||
expect(
|
|
||||||
screen.queryByText(
|
|
||||||
/You have referenced something which has not yet been labelled/
|
|
||||||
)
|
|
||||||
).to.not.exist
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,437 +0,0 @@
|
||||||
import PdfSynctexControls from '../../../../../frontend/js/features/pdf-preview/components/pdf-synctex-controls'
|
|
||||||
import { renderWithEditorContext } from '../../../helpers/render-with-context'
|
|
||||||
import sysendTestHelper from '../../../helpers/sysend'
|
|
||||||
import { cloneDeep } from 'lodash'
|
|
||||||
import fetchMock from 'fetch-mock'
|
|
||||||
import { fireEvent, screen, waitFor } from '@testing-library/react'
|
|
||||||
import fs from 'fs'
|
|
||||||
import path from 'path'
|
|
||||||
import { expect } from 'chai'
|
|
||||||
import { useDetachCompileContext as useCompileContext } from '../../../../../frontend/js/shared/context/detach-compile-context'
|
|
||||||
import { useFileTreeData } from '../../../../../frontend/js/shared/context/file-tree-data-context'
|
|
||||||
import { useEffect } from 'react'
|
|
||||||
|
|
||||||
const examplePDF = path.join(__dirname, '../fixtures/test-example.pdf')
|
|
||||||
|
|
||||||
const scope = {
|
|
||||||
settings: {
|
|
||||||
syntaxValidation: false,
|
|
||||||
pdfViewer: 'pdfjs',
|
|
||||||
},
|
|
||||||
editor: {
|
|
||||||
sharejs_doc: {
|
|
||||||
doc_id: 'test-doc',
|
|
||||||
getSnapshot: () => 'some doc content',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const outputFiles = [
|
|
||||||
{
|
|
||||||
path: 'output.pdf',
|
|
||||||
build: '123',
|
|
||||||
url: '/build/output.pdf',
|
|
||||||
type: 'pdf',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'output.log',
|
|
||||||
build: '123',
|
|
||||||
url: '/build/output.log',
|
|
||||||
type: 'log',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const mockCompile = () =>
|
|
||||||
fetchMock.post('express:/project/:projectId/compile', {
|
|
||||||
body: {
|
|
||||||
status: 'success',
|
|
||||||
clsiServerId: 'foo',
|
|
||||||
compileGroup: 'standard',
|
|
||||||
pdfDownloadDomain: 'https://clsi.test-overleaf.com',
|
|
||||||
outputFiles: cloneDeep(outputFiles),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const fileResponses = {
|
|
||||||
'/build/output.pdf': () => fs.createReadStream(examplePDF),
|
|
||||||
'/build/output.log': '',
|
|
||||||
}
|
|
||||||
|
|
||||||
const mockBuildFile = () =>
|
|
||||||
fetchMock.get('begin:https://clsi.test-overleaf.com/', _url => {
|
|
||||||
const url = new URL(_url, 'https://clsi.test-overleaf.com')
|
|
||||||
|
|
||||||
if (url.pathname in fileResponses) {
|
|
||||||
return fileResponses[url.pathname]
|
|
||||||
}
|
|
||||||
|
|
||||||
return 404
|
|
||||||
})
|
|
||||||
|
|
||||||
const mockHighlights = [
|
|
||||||
{
|
|
||||||
page: 1,
|
|
||||||
h: 85.03936,
|
|
||||||
v: 509.999878,
|
|
||||||
width: 441.921265,
|
|
||||||
height: 8.855677,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
page: 1,
|
|
||||||
h: 85.03936,
|
|
||||||
v: 486.089539,
|
|
||||||
width: 441.921265,
|
|
||||||
height: 8.855677,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const mockPosition = {
|
|
||||||
page: 1,
|
|
||||||
offset: { top: 10, left: 10 },
|
|
||||||
pageSize: { height: 500, width: 500 },
|
|
||||||
}
|
|
||||||
|
|
||||||
const mockSelectedEntities = [{ type: 'doc' }]
|
|
||||||
|
|
||||||
const mockSynctex = () =>
|
|
||||||
fetchMock
|
|
||||||
.get('express:/project/:projectId/sync/code', () => {
|
|
||||||
return { pdf: cloneDeep(mockHighlights) }
|
|
||||||
})
|
|
||||||
.get('express:/project/:projectId/sync/pdf', () => {
|
|
||||||
return { code: [{ file: 'main.tex', line: 100 }] }
|
|
||||||
})
|
|
||||||
|
|
||||||
const WithPosition = ({ mockPosition }) => {
|
|
||||||
const { setPosition } = useCompileContext()
|
|
||||||
|
|
||||||
// mock PDF scroll position update
|
|
||||||
useEffect(() => {
|
|
||||||
setPosition(mockPosition)
|
|
||||||
}, [mockPosition, setPosition])
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const WithSelectedEntities = ({ mockSelectedEntities = [] }) => {
|
|
||||||
const { setSelectedEntities } = useFileTreeData()
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setSelectedEntities(mockSelectedEntities)
|
|
||||||
}, [mockSelectedEntities, setSelectedEntities])
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
describe('<PdfSynctexControls/>', function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
window.metaAttributesCache = new Map()
|
|
||||||
fetchMock.restore()
|
|
||||||
mockCompile()
|
|
||||||
mockSynctex()
|
|
||||||
mockBuildFile()
|
|
||||||
})
|
|
||||||
|
|
||||||
afterEach(function () {
|
|
||||||
window.metaAttributesCache = new Map()
|
|
||||||
fetchMock.restore()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('handles clicks on sync buttons', async function () {
|
|
||||||
const { container } = renderWithEditorContext(
|
|
||||||
<>
|
|
||||||
<WithPosition mockPosition={mockPosition} />
|
|
||||||
<WithSelectedEntities mockSelectedEntities={mockSelectedEntities} />
|
|
||||||
<PdfSynctexControls />
|
|
||||||
</>,
|
|
||||||
{ scope }
|
|
||||||
)
|
|
||||||
|
|
||||||
const syncToPdfButton = await screen.findByRole('button', {
|
|
||||||
name: 'Go to code location in PDF',
|
|
||||||
})
|
|
||||||
|
|
||||||
const syncToCodeButton = await screen.findByRole('button', {
|
|
||||||
name: /Go to PDF location in code/,
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(container.querySelectorAll('.synctex-control-icon').length).to.equal(
|
|
||||||
2
|
|
||||||
)
|
|
||||||
|
|
||||||
// mock editor cursor position update
|
|
||||||
fireEvent(
|
|
||||||
window,
|
|
||||||
new CustomEvent('cursor:editor:update', {
|
|
||||||
detail: { row: 100, column: 10 },
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
fireEvent.click(syncToPdfButton)
|
|
||||||
|
|
||||||
expect(syncToPdfButton.disabled).to.be.true
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(fetchMock.called('express:/project/:projectId/sync/code')).to.be
|
|
||||||
.true
|
|
||||||
})
|
|
||||||
|
|
||||||
fireEvent.click(syncToCodeButton)
|
|
||||||
|
|
||||||
expect(syncToCodeButton.disabled).to.be.true
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(fetchMock.called('express:/project/:projectId/sync/pdf')).to.be
|
|
||||||
.true
|
|
||||||
})
|
|
||||||
})
|
|
||||||
it('disables button when multiple entities are selected', async function () {
|
|
||||||
renderWithEditorContext(
|
|
||||||
<>
|
|
||||||
<WithPosition mockPosition={mockPosition} />
|
|
||||||
<WithSelectedEntities
|
|
||||||
mockSelectedEntities={[{ type: 'doc' }, { type: 'doc' }]}
|
|
||||||
/>
|
|
||||||
<PdfSynctexControls />
|
|
||||||
</>,
|
|
||||||
{ scope }
|
|
||||||
)
|
|
||||||
|
|
||||||
const syncToPdfButton = await screen.findByRole('button', {
|
|
||||||
name: 'Go to code location in PDF',
|
|
||||||
})
|
|
||||||
expect(syncToPdfButton.disabled).to.be.true
|
|
||||||
|
|
||||||
const syncToCodeButton = await screen.findByRole('button', {
|
|
||||||
name: /Go to PDF location in code/,
|
|
||||||
})
|
|
||||||
expect(syncToCodeButton.disabled).to.be.true
|
|
||||||
})
|
|
||||||
|
|
||||||
it('disables button when a file is selected', async function () {
|
|
||||||
renderWithEditorContext(
|
|
||||||
<>
|
|
||||||
<WithPosition mockPosition={mockPosition} />
|
|
||||||
<WithSelectedEntities mockSelectedEntities={[{ type: 'file' }]} />
|
|
||||||
<PdfSynctexControls />
|
|
||||||
</>,
|
|
||||||
{ scope }
|
|
||||||
)
|
|
||||||
|
|
||||||
const syncToPdfButton = await screen.findByRole('button', {
|
|
||||||
name: 'Go to code location in PDF',
|
|
||||||
})
|
|
||||||
expect(syncToPdfButton.disabled).to.be.true
|
|
||||||
|
|
||||||
const syncToCodeButton = await screen.findByRole('button', {
|
|
||||||
name: /Go to PDF location in code/,
|
|
||||||
})
|
|
||||||
expect(syncToCodeButton.disabled).to.be.true
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('with detacher role', async function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
window.metaAttributesCache.set('ol-detachRole', 'detacher')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('does not have go to PDF location button nor arrow icon', async function () {
|
|
||||||
const { container } = renderWithEditorContext(
|
|
||||||
<>
|
|
||||||
<WithPosition mockPosition={mockPosition} />
|
|
||||||
<WithSelectedEntities mockSelectedEntities={mockSelectedEntities} />
|
|
||||||
<PdfSynctexControls />
|
|
||||||
</>,
|
|
||||||
{ scope }
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(
|
|
||||||
await screen.queryByRole('button', {
|
|
||||||
name: 'Go to PDF location in code',
|
|
||||||
})
|
|
||||||
).to.not.exist
|
|
||||||
|
|
||||||
expect(container.querySelector('.synctex-control-icon')).to.not.exist
|
|
||||||
})
|
|
||||||
|
|
||||||
it('send set highlights action', async function () {
|
|
||||||
renderWithEditorContext(
|
|
||||||
<>
|
|
||||||
<WithPosition mockPosition={mockPosition} />
|
|
||||||
<WithSelectedEntities mockSelectedEntities={mockSelectedEntities} />
|
|
||||||
<PdfSynctexControls />
|
|
||||||
</>,
|
|
||||||
{ scope }
|
|
||||||
)
|
|
||||||
sysendTestHelper.resetHistory()
|
|
||||||
|
|
||||||
const syncToPdfButton = await screen.findByRole('button', {
|
|
||||||
name: 'Go to code location in PDF',
|
|
||||||
})
|
|
||||||
|
|
||||||
// mock editor cursor position update
|
|
||||||
fireEvent(
|
|
||||||
window,
|
|
||||||
new CustomEvent('cursor:editor:update', {
|
|
||||||
detail: { row: 100, column: 10 },
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(syncToPdfButton.disabled).to.be.false
|
|
||||||
|
|
||||||
fireEvent.click(syncToPdfButton)
|
|
||||||
|
|
||||||
expect(syncToPdfButton.disabled).to.be.true
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(fetchMock.called('express:/project/:projectId/sync/code')).to.be
|
|
||||||
.true
|
|
||||||
})
|
|
||||||
|
|
||||||
// synctex is called locally and the result are broadcast for the detached
|
|
||||||
// tab
|
|
||||||
expect(sysendTestHelper.getLastBroacastMessage()).to.deep.equal({
|
|
||||||
role: 'detacher',
|
|
||||||
event: 'action-setHighlights',
|
|
||||||
data: { args: [mockHighlights] },
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('reacts to sync to code action', async function () {
|
|
||||||
renderWithEditorContext(
|
|
||||||
<>
|
|
||||||
<WithPosition mockPosition={mockPosition} />
|
|
||||||
<WithSelectedEntities mockSelectedEntities={mockSelectedEntities} />
|
|
||||||
<PdfSynctexControls />
|
|
||||||
</>,
|
|
||||||
{ scope }
|
|
||||||
)
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(fetchMock.called('express:/project/:projectId/compile')).to.be
|
|
||||||
.true
|
|
||||||
})
|
|
||||||
|
|
||||||
sysendTestHelper.receiveMessage({
|
|
||||||
role: 'detached',
|
|
||||||
event: 'action-sync-to-code',
|
|
||||||
data: {
|
|
||||||
args: [mockPosition],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(fetchMock.called('express:/project/:projectId/sync/pdf')).to.be
|
|
||||||
.true
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('with detached role', async function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
window.metaAttributesCache.set('ol-detachRole', 'detached')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('does not have go to code location button nor arrow icon', async function () {
|
|
||||||
const { container } = renderWithEditorContext(
|
|
||||||
<>
|
|
||||||
<WithPosition mockPosition={mockPosition} />
|
|
||||||
<PdfSynctexControls />
|
|
||||||
</>,
|
|
||||||
{ scope }
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(
|
|
||||||
await screen.queryByRole('button', {
|
|
||||||
name: 'Go to code location in PDF',
|
|
||||||
})
|
|
||||||
).to.not.exist
|
|
||||||
|
|
||||||
expect(container.querySelector('.synctex-control-icon')).to.not.exist
|
|
||||||
})
|
|
||||||
|
|
||||||
it('send go to code line action', async function () {
|
|
||||||
const { container } = renderWithEditorContext(
|
|
||||||
<>
|
|
||||||
<WithPosition mockPosition={mockPosition} />
|
|
||||||
<PdfSynctexControls />
|
|
||||||
</>,
|
|
||||||
{ scope }
|
|
||||||
)
|
|
||||||
|
|
||||||
const syncToCodeButton = await screen.findByRole('button', {
|
|
||||||
name: /Go to PDF location in code/,
|
|
||||||
})
|
|
||||||
expect(syncToCodeButton.disabled).to.be.true
|
|
||||||
|
|
||||||
sysendTestHelper.receiveMessage({
|
|
||||||
role: 'detached',
|
|
||||||
event: 'state-has-single-selected-doc',
|
|
||||||
data: { value: true },
|
|
||||||
})
|
|
||||||
expect(syncToCodeButton.disabled).to.be.false
|
|
||||||
|
|
||||||
sysendTestHelper.resetHistory()
|
|
||||||
|
|
||||||
fireEvent.click(syncToCodeButton)
|
|
||||||
|
|
||||||
// the button is only disabled when the state is updated via sysend
|
|
||||||
expect(syncToCodeButton.disabled).to.be.false
|
|
||||||
expect(container.querySelectorAll('.synctex-spin-icon').length).to.equal(
|
|
||||||
0
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(sysendTestHelper.getLastBroacastMessage()).to.deep.equal({
|
|
||||||
role: 'detached',
|
|
||||||
event: 'action-sync-to-code',
|
|
||||||
data: {
|
|
||||||
args: [mockPosition, 72],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('update inflight state', async function () {
|
|
||||||
const { container } = renderWithEditorContext(
|
|
||||||
<>
|
|
||||||
<WithPosition mockPosition={mockPosition} />
|
|
||||||
<PdfSynctexControls />
|
|
||||||
</>,
|
|
||||||
{ scope }
|
|
||||||
)
|
|
||||||
sysendTestHelper.receiveMessage({
|
|
||||||
role: 'detached',
|
|
||||||
event: 'state-has-single-selected-doc',
|
|
||||||
data: { value: true },
|
|
||||||
})
|
|
||||||
|
|
||||||
const syncToCodeButton = await screen.findByRole('button', {
|
|
||||||
name: /Go to PDF location in code/,
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(syncToCodeButton.disabled).to.be.false
|
|
||||||
expect(container.querySelectorAll('.synctex-spin-icon').length).to.equal(
|
|
||||||
0
|
|
||||||
)
|
|
||||||
|
|
||||||
sysendTestHelper.receiveMessage({
|
|
||||||
role: 'detacher',
|
|
||||||
event: 'state-sync-to-code-inflight',
|
|
||||||
data: { value: true },
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(syncToCodeButton.disabled).to.be.true
|
|
||||||
expect(container.querySelectorAll('.synctex-spin-icon').length).to.equal(
|
|
||||||
1
|
|
||||||
)
|
|
||||||
|
|
||||||
sysendTestHelper.receiveMessage({
|
|
||||||
role: 'detacher',
|
|
||||||
event: 'state-sync-to-code-inflight',
|
|
||||||
data: { value: false },
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(syncToCodeButton.disabled).to.be.false
|
|
||||||
expect(container.querySelectorAll('.synctex-spin-icon').length).to.equal(
|
|
||||||
0
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,148 +0,0 @@
|
||||||
import fetchMock from 'fetch-mock'
|
|
||||||
import { cloneDeep } from 'lodash'
|
|
||||||
import nock from 'nock'
|
|
||||||
import fs from 'fs'
|
|
||||||
import path from 'path'
|
|
||||||
|
|
||||||
export const examplePDF = path.join(__dirname, '../fixtures/test-example.pdf')
|
|
||||||
export const corruptPDF = path.join(
|
|
||||||
__dirname,
|
|
||||||
'../fixtures/test-example-corrupt.pdf'
|
|
||||||
)
|
|
||||||
|
|
||||||
const outputFiles = [
|
|
||||||
{
|
|
||||||
path: 'output.pdf',
|
|
||||||
build: '123',
|
|
||||||
url: '/build/output.pdf',
|
|
||||||
type: 'pdf',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'output.bbl',
|
|
||||||
build: '123',
|
|
||||||
url: '/build/output.bbl',
|
|
||||||
type: 'bbl',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'output.bib',
|
|
||||||
build: '123',
|
|
||||||
url: '/build/output.bib',
|
|
||||||
type: 'bib',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'example.txt',
|
|
||||||
build: '123',
|
|
||||||
url: '/build/example.txt',
|
|
||||||
type: 'txt',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'output.log',
|
|
||||||
build: '123',
|
|
||||||
url: '/build/output.log',
|
|
||||||
type: 'log',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'output.blg',
|
|
||||||
build: '123',
|
|
||||||
url: '/build/output.blg',
|
|
||||||
type: 'blg',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
export const mockCompile = (delayPromise = Promise.resolve()) =>
|
|
||||||
fetchMock.post(
|
|
||||||
'express:/project/:projectId/compile',
|
|
||||||
delayPromise.then(() => ({
|
|
||||||
body: {
|
|
||||||
status: 'success',
|
|
||||||
clsiServerId: 'foo',
|
|
||||||
compileGroup: 'priority',
|
|
||||||
pdfDownloadDomain: 'https://clsi.test-overleaf.com',
|
|
||||||
outputFiles: cloneDeep(outputFiles),
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
|
|
||||||
export const mockCompileError = status =>
|
|
||||||
fetchMock.post('express:/project/:projectId/compile', {
|
|
||||||
body: {
|
|
||||||
status,
|
|
||||||
clsiServerId: 'foo',
|
|
||||||
compileGroup: 'priority',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
export const mockValidationProblems = validationProblems =>
|
|
||||||
fetchMock.post('express:/project/:projectId/compile', {
|
|
||||||
body: {
|
|
||||||
status: 'validation-problems',
|
|
||||||
validationProblems,
|
|
||||||
clsiServerId: 'foo',
|
|
||||||
compileGroup: 'priority',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
export const mockClearCache = (delayPromise = Promise.resolve()) =>
|
|
||||||
fetchMock.delete(
|
|
||||||
'express:/project/:projectId/output',
|
|
||||||
delayPromise.then(() => ({
|
|
||||||
body: {
|
|
||||||
status: 204,
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
|
|
||||||
export const mockValidPdf = () => {
|
|
||||||
nock('https://clsi.test-overleaf.com')
|
|
||||||
.get(/^\/build\/output\.pdf/)
|
|
||||||
.replyWithFile(200, examplePDF)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const defaultFileResponses = {
|
|
||||||
'/build/output.pdf': () => fs.createReadStream(examplePDF),
|
|
||||||
'/build/output.blg': 'This is BibTeX, Version 4.0', // FIXME
|
|
||||||
'/build/output.log': `
|
|
||||||
The LaTeX compiler output
|
|
||||||
* With a lot of details
|
|
||||||
|
|
||||||
Wrapped in an HTML <pre> element with
|
|
||||||
preformatted text which is to be presented exactly
|
|
||||||
as written in the HTML file
|
|
||||||
|
|
||||||
(whitespace included™)
|
|
||||||
|
|
||||||
The text is typically rendered using a non-proportional ("monospace") font.
|
|
||||||
|
|
||||||
LaTeX Font Info: External font \`cmex10' loaded for size
|
|
||||||
(Font) <7> on input line 18.
|
|
||||||
LaTeX Font Info: External font \`cmex10' loaded for size
|
|
||||||
(Font) <5> on input line 18.
|
|
||||||
! Undefined control sequence.
|
|
||||||
<recently read> \\Zlpha
|
|
||||||
|
|
||||||
main.tex, line 23
|
|
||||||
|
|
||||||
`,
|
|
||||||
}
|
|
||||||
|
|
||||||
export const mockBuildFile = (responses = defaultFileResponses) => {
|
|
||||||
fetchMock.get('begin:https://clsi.test-overleaf.com/', _url => {
|
|
||||||
const url = new URL(_url, 'https://clsi.test-overleaf.com')
|
|
||||||
|
|
||||||
if (url.pathname in responses) {
|
|
||||||
return responses[url.pathname]
|
|
||||||
}
|
|
||||||
|
|
||||||
return 404
|
|
||||||
})
|
|
||||||
|
|
||||||
fetchMock.get('express:/build/:file', (_url, options, request) => {
|
|
||||||
const url = new URL(_url, 'https://example.com')
|
|
||||||
|
|
||||||
if (url.pathname in responses) {
|
|
||||||
return responses[url.pathname]
|
|
||||||
}
|
|
||||||
|
|
||||||
return 404
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -28,7 +28,7 @@ export function EditorProviders({
|
||||||
},
|
},
|
||||||
isRestrictedTokenMember = false,
|
isRestrictedTokenMember = false,
|
||||||
clsiServerId = '1234',
|
clsiServerId = '1234',
|
||||||
scope,
|
scope = {},
|
||||||
features = {
|
features = {
|
||||||
referencesSearch: true,
|
referencesSearch: true,
|
||||||
},
|
},
|
||||||
|
|
|
@ -10,13 +10,15 @@
|
||||||
"moduleResolution": "node" /* Specify module resolution strategy */,
|
"moduleResolution": "node" /* Specify module resolution strategy */,
|
||||||
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
|
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
|
||||||
"skipLibCheck": true /* Skip type checking of declaration files. */,
|
"skipLibCheck": true /* Skip type checking of declaration files. */,
|
||||||
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
|
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */,
|
||||||
|
"types": ["cypress", "@testing-library/cypress"]
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"frontend/js/**/*.*",
|
"frontend/js/**/*.*",
|
||||||
"modules/**/frontend/js/**/*.*",
|
"modules/**/frontend/js/**/*.*",
|
||||||
"test/frontend/**/*.*",
|
"test/frontend/**/*.*",
|
||||||
"modules/**/test/frontend/**/*.*",
|
"modules/**/test/frontend/**/*.*",
|
||||||
|
"cypress",
|
||||||
"types"
|
"types"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,11 @@ declare global {
|
||||||
user: {
|
user: {
|
||||||
id: string
|
id: string
|
||||||
}
|
}
|
||||||
|
metaAttributesCache: Map<string, unknown>
|
||||||
|
i18n: {
|
||||||
|
currentLangCode: string
|
||||||
|
}
|
||||||
|
ExposedSettings: Record<string, unknown>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export {} // pretend this is a module
|
export {} // pretend this is a module
|
||||||
|
|
|
@ -234,10 +234,12 @@ module.exports = {
|
||||||
output: 'manifest.json',
|
output: 'manifest.json',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// Ensure that process.env.RESET_APP_DATA_TIMER is defined, to avoid an error.
|
|
||||||
// https://github.com/algolia/algoliasearch-client-javascript/issues/756
|
|
||||||
new webpack.EnvironmentPlugin({
|
new webpack.EnvironmentPlugin({
|
||||||
|
// Ensure that process.env.RESET_APP_DATA_TIMER is defined, to avoid an error.
|
||||||
|
// https://github.com/algolia/algoliasearch-client-javascript/issues/756
|
||||||
RESET_APP_DATA_TIMER: '120000',
|
RESET_APP_DATA_TIMER: '120000',
|
||||||
|
// Ensure that process.env.CYPRESS is defined (see utils/worker.js)
|
||||||
|
CYPRESS: false,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// Prevent moment from loading (very large) locale files that aren't used
|
// Prevent moment from loading (very large) locale files that aren't used
|
||||||
|
|
Loading…
Reference in a new issue