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
|
||||
"files": ["cypress/**/*.js", "**/test/frontend/**/*.spec.js"],
|
||||
"files": ["cypress/**/*.{js,ts,tsx}", "**/test/frontend/**/*.spec.{js,ts,tsx}"],
|
||||
"extends": [
|
||||
"plugin:cypress/recommended"
|
||||
]
|
||||
|
|
2
services/web/.gitignore
vendored
2
services/web/.gitignore
vendored
|
@ -91,3 +91,5 @@ cypress/downloads/
|
|||
|
||||
# Ace themes for conversion
|
||||
modules/source-editor/frontend/js/themes/ace/
|
||||
|
||||
!**/fixtures/**/*.log
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
{
|
||||
"component": {
|
||||
"componentFolder": ".",
|
||||
"testFiles": "./{test,modules/**/test}/frontend/components/**/*.spec.js",
|
||||
"supportFile": "cypress/support/ct/index.js"
|
||||
"testFiles": "./{test,modules/**/test}/frontend/components/**/*.spec.{js,ts,tsx}",
|
||||
"supportFile": "cypress/support/ct/index.ts"
|
||||
},
|
||||
"experimentalFetchPolyfill": true,
|
||||
"fixturesFolder": false,
|
||||
"fixturesFolder": "cypress/fixtures",
|
||||
"video": false,
|
||||
"viewportHeight": 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 { merge } = require('webpack-merge')
|
||||
const path = require('path')
|
||||
const webpack = require('webpack')
|
||||
const devConfig = require('../../webpack.config.dev')
|
||||
|
||||
const webpackConfig = merge(devConfig, {
|
||||
devServer: {
|
||||
static: path.join(__dirname, '../../../../public'),
|
||||
static: path.join(__dirname, '../../public'),
|
||||
},
|
||||
stats: 'none',
|
||||
plugins: [
|
||||
new webpack.EnvironmentPlugin({
|
||||
CYPRESS: true,
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
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 => {
|
||||
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.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 = {
|
||||
tooltip: PropTypes.exact({
|
||||
tooltip: PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
text: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired,
|
||||
placement: PropTypes.string,
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
export const createWorker = callback => {
|
||||
if (process.env.CYPRESS) {
|
||||
return callback()
|
||||
}
|
||||
const webpackPublicPath = __webpack_public_path__
|
||||
__webpack_public_path__ = '/'
|
||||
callback()
|
||||
|
|
|
@ -116,6 +116,7 @@
|
|||
"daterangepicker": "https://github.com/overleaf/daterangepicker/archive/e496d2d44ca53e208c930e4cb4bcf29bcefa4550.tar.gz",
|
||||
"downshift": "^6.1.0",
|
||||
"east": "^2.0.2",
|
||||
"events": "^3.3.0",
|
||||
"express": "4.17.1",
|
||||
"express-bearer-token": "^2.4.0",
|
||||
"express-http-proxy": "^1.6.0",
|
||||
|
@ -218,6 +219,7 @@
|
|||
"@testing-library/react": "^11.2.7",
|
||||
"@testing-library/react-hooks": "^7.0.0",
|
||||
"@types/chai": "^4.3.0",
|
||||
"@types/events": "^3.0.0",
|
||||
"@types/mocha": "^9.1.0",
|
||||
"@types/react": "^17.0.40",
|
||||
"@types/react-bootstrap": "^0.32.29",
|
||||
|
@ -264,6 +266,7 @@
|
|||
"fetch-mock": "^9.10.2",
|
||||
"glob": "^7.1.6",
|
||||
"handlebars-loader": "^1.7.1",
|
||||
"html-webpack-plugin": "^5.5.0",
|
||||
"i18next-scanner": "^3.0.0",
|
||||
"jsdom": "^19.0.0",
|
||||
"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 () {
|
||||
const setContextMenuCoords = sinon.stub()
|
||||
|
||||
beforeEach(function () {
|
||||
global.requestAnimationFrame = sinon.stub()
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
setContextMenuCoords.reset()
|
||||
delete global.requestAnimationFrame
|
||||
})
|
||||
|
||||
describe('menu', function () {
|
||||
|
|
|
@ -14,13 +14,11 @@ describe('<FileTreeRoot/>', function () {
|
|||
const onInit = sinon.stub()
|
||||
|
||||
beforeEach(function () {
|
||||
global.requestAnimationFrame = sinon.stub()
|
||||
window.metaAttributesCache = new Map()
|
||||
window.metaAttributesCache.set('ol-user', { id: 'user1' })
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
delete global.requestAnimationFrame
|
||||
fetchMock.restore()
|
||||
onSelect.reset()
|
||||
onInit.reset()
|
||||
|
|
|
@ -15,13 +15,11 @@ describe('FileTree Create Folder Flow', function () {
|
|||
const onInit = sinon.stub()
|
||||
|
||||
beforeEach(function () {
|
||||
global.requestAnimationFrame = sinon.stub()
|
||||
window.metaAttributesCache = new Map()
|
||||
window.metaAttributesCache.set('ol-user', { id: 'user1' })
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
delete global.requestAnimationFrame
|
||||
fetchMock.restore()
|
||||
onSelect.reset()
|
||||
onInit.reset()
|
||||
|
|
|
@ -15,13 +15,11 @@ describe('FileTree Rename Entity Flow', function () {
|
|||
const onInit = sinon.stub()
|
||||
|
||||
beforeEach(function () {
|
||||
global.requestAnimationFrame = sinon.stub()
|
||||
window.metaAttributesCache = new Map()
|
||||
window.metaAttributesCache.set('ol-user', { id: 'user1' })
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
delete global.requestAnimationFrame
|
||||
fetchMock.restore()
|
||||
onSelect.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,
|
||||
clsiServerId = '1234',
|
||||
scope,
|
||||
scope = {},
|
||||
features = {
|
||||
referencesSearch: true,
|
||||
},
|
||||
|
|
|
@ -10,13 +10,15 @@
|
|||
"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'. */,
|
||||
"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": [
|
||||
"frontend/js/**/*.*",
|
||||
"modules/**/frontend/js/**/*.*",
|
||||
"test/frontend/**/*.*",
|
||||
"modules/**/test/frontend/**/*.*",
|
||||
"cypress",
|
||||
"types"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -5,6 +5,11 @@ declare global {
|
|||
user: {
|
||||
id: string
|
||||
}
|
||||
metaAttributesCache: Map<string, unknown>
|
||||
i18n: {
|
||||
currentLangCode: string
|
||||
}
|
||||
ExposedSettings: Record<string, unknown>
|
||||
}
|
||||
}
|
||||
export {} // pretend this is a module
|
||||
|
|
|
@ -234,10 +234,12 @@ module.exports = {
|
|||
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({
|
||||
// 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',
|
||||
// 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
|
||||
|
|
Loading…
Reference in a new issue