diff --git a/package-lock.json b/package-lock.json index 592ca32f8c..0c91b38963 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5682,6 +5682,12 @@ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==" }, + "node_modules/@types/events": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", + "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==", + "dev": true + }, "node_modules/@types/express": { "version": "4.17.13", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", @@ -5741,11 +5747,10 @@ } }, "node_modules/@types/html-minifier-terser": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.2.tgz", - "integrity": "sha512-h4lTMgMJctJybDp8CQrxTUiiYmedihHWkjnF/8Pxseu2S6Nlfcy8kwboQ8yejh456rP2yWoEVm1sS/FVsfM48w==", - "dev": true, - "peer": true + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", + "dev": true }, "node_modules/@types/http-proxy": { "version": "1.17.8", @@ -6047,13 +6052,6 @@ "@types/node": "*" } }, - "node_modules/@types/source-list-map": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", - "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==", - "dev": true, - "peer": true - }, "node_modules/@types/superagent": { "version": "3.8.7", "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-3.8.7.tgz", @@ -6064,90 +6062,16 @@ "@types/node": "*" } }, - "node_modules/@types/tapable": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.8.tgz", - "integrity": "sha512-ipixuVrh2OdNmauvtT51o3d8z12p6LtFW9in7U79der/kwejjdNchQC5UMn5u/KxNoM7VHHOs/l8KS8uHxhODQ==", - "dev": true, - "peer": true - }, "node_modules/@types/tough-cookie": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.1.tgz", "integrity": "sha512-Y0K95ThC3esLEYD6ZuqNek29lNX2EM1qxV8y2FTLUB0ff5wWrk7az+mLrnNFUnaXcgKye22+sFBRXOgpPILZNg==" }, - "node_modules/@types/uglify-js": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.13.1.tgz", - "integrity": "sha512-O3MmRAk6ZuAKa9CHgg0Pr0+lUOqoMLpc9AS4R8ano2auvsg7IE8syF3Xh/NPr26TWklxYcqoEEFdzLLs1fV9PQ==", - "dev": true, - "peer": true, - "dependencies": { - "source-map": "^0.6.1" - } - }, - "node_modules/@types/uglify-js/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/@types/underscore": { "version": "1.11.4", "resolved": "https://registry.npmjs.org/@types/underscore/-/underscore-1.11.4.tgz", "integrity": "sha512-uO4CD2ELOjw8tasUrAhvnn2W4A0ZECOvMjCivJr4gA9pGgjv+qxKWY9GLTMVEK8ej85BxQOocUyE7hImmSQYcg==" }, - "node_modules/@types/webpack": { - "version": "4.41.32", - "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.32.tgz", - "integrity": "sha512-cb+0ioil/7oz5//7tZUSwbrSAN/NWHrQylz5cW8G0dWTcF/g+/dSdMlKVZspBYuMAN1+WnwHrkxiRrLcwd0Heg==", - "dev": true, - "peer": true, - "dependencies": { - "@types/node": "*", - "@types/tapable": "^1", - "@types/uglify-js": "*", - "@types/webpack-sources": "*", - "anymatch": "^3.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/@types/webpack-sources": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-3.2.0.tgz", - "integrity": "sha512-Ft7YH3lEVRQ6ls8k4Ff1oB4jN6oy/XmU6tQISKdhfh+1mR+viZFphS6WL0IrtDOzvefmJg5a0s7ZQoRXwqTEFg==", - "dev": true, - "peer": true, - "dependencies": { - "@types/node": "*", - "@types/source-list-map": "*", - "source-map": "^0.7.3" - } - }, - "node_modules/@types/webpack-sources/node_modules/source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@types/webpack/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/@types/ws": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-6.0.4.tgz", @@ -9049,7 +8973,6 @@ "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", "dev": true, - "peer": true, "dependencies": { "pascal-case": "^3.1.2", "tslib": "^2.0.3" @@ -9468,16 +9391,15 @@ "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==" }, "node_modules/clean-css": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.4.tgz", - "integrity": "sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.0.tgz", + "integrity": "sha512-YYuuxv4H/iNb1Z/5IbMRoxgrzjWGhOEFfd+groZ5dMCVkpENiMZmwspdrzBo9286JjM1gZJPAyL7ZIdzuvu2AQ==", "dev": true, - "peer": true, "dependencies": { "source-map": "~0.6.0" }, "engines": { - "node": ">= 4.0" + "node": ">= 10.0" } }, "node_modules/clean-css/node_modules/source-map": { @@ -9485,7 +9407,6 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, - "peer": true, "engines": { "node": ">=0.10.0" } @@ -11825,7 +11746,6 @@ "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", "dev": true, - "peer": true, "dependencies": { "utila": "~0.4" } @@ -11931,7 +11851,6 @@ "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", "dev": true, - "peer": true, "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" @@ -12331,14 +12250,6 @@ "node": ">=10.13.0" } }, - "node_modules/enhanced-resolve/node_modules/tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "engines": { - "node": ">=6" - } - }, "node_modules/enquirer": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", @@ -16384,25 +16295,33 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==" }, "node_modules/html-minifier-terser": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz", - "integrity": "sha512-ZPr5MNObqnV/T9akshPKbVgyOqLmy+Bxo7juKCfTfnjNniTAMdy4hz21YQqoofMBJD2kdREaqPPdThoR78Tgxg==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", "dev": true, - "peer": true, "dependencies": { - "camel-case": "^4.1.1", - "clean-css": "^4.2.3", - "commander": "^4.1.1", + "camel-case": "^4.1.2", + "clean-css": "^5.2.2", + "commander": "^8.3.0", "he": "^1.2.0", - "param-case": "^3.0.3", + "param-case": "^3.0.4", "relateurl": "^0.2.7", - "terser": "^4.6.3" + "terser": "^5.10.0" }, "bin": { "html-minifier-terser": "cli.js" }, "engines": { - "node": ">=6" + "node": ">=12" + } + }, + "node_modules/html-minifier-terser/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "engines": { + "node": ">= 12" } }, "node_modules/html-parse-stringify": { @@ -16422,83 +16341,26 @@ } }, "node_modules/html-webpack-plugin": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-4.5.2.tgz", - "integrity": "sha512-q5oYdzjKUIPQVjOosjgvCHQOv9Ett9CYYHlgvJeXG0qQvdSojnBq4vAdQBwn1+yGveAwHCoe/rMR86ozX3+c2A==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz", + "integrity": "sha512-sy88PC2cRTVxvETRgUHFrL4No3UxvcH8G1NepGhqaTT+GXN2kTamqasot0inS5hXeg1cMbFDt27zzo9p35lZVw==", "dev": true, - "peer": true, "dependencies": { - "@types/html-minifier-terser": "^5.0.0", - "@types/tapable": "^1.0.5", - "@types/webpack": "^4.41.8", - "html-minifier-terser": "^5.0.1", - "loader-utils": "^1.2.3", - "lodash": "^4.17.20", - "pretty-error": "^2.1.1", - "tapable": "^1.1.3", - "util.promisify": "1.0.0" + "@types/html-minifier-terser": "^6.0.0", + "html-minifier-terser": "^6.0.2", + "lodash": "^4.17.21", + "pretty-error": "^4.0.0", + "tapable": "^2.0.0" }, "engines": { - "node": ">=6.9" + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/html-webpack-plugin" }, "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" - } - }, - "node_modules/html-webpack-plugin/node_modules/big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true, - "peer": true, - "engines": { - "node": "*" - } - }, - "node_modules/html-webpack-plugin/node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "peer": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/html-webpack-plugin/node_modules/loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "peer": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/html-webpack-plugin/node_modules/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true, - "peer": true - }, - "node_modules/html-webpack-plugin/node_modules/util.promisify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", - "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", - "dev": true, - "peer": true, - "dependencies": { - "define-properties": "^1.1.2", - "object.getownpropertydescriptors": "^2.0.3" + "webpack": "^5.20.0" } }, "node_modules/htmlparser2": { @@ -20375,7 +20237,6 @@ "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", "dev": true, - "peer": true, "dependencies": { "tslib": "^2.0.3" } @@ -21942,7 +21803,6 @@ "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", "dev": true, - "peer": true, "dependencies": { "lower-case": "^2.0.2", "tslib": "^2.0.3" @@ -23335,7 +23195,6 @@ "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", "dev": true, - "peer": true, "dependencies": { "dot-case": "^3.0.4", "tslib": "^2.0.3" @@ -23449,7 +23308,6 @@ "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", "dev": true, - "peer": true, "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" @@ -24233,14 +24091,13 @@ } }, "node_modules/pretty-error": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.2.tgz", - "integrity": "sha512-EY5oDzmsX5wvuynAByrmY0P0hcp+QpnAKbJng2A2MPjVKXCxrDSUkzghVJ4ZGPIv+JC4gX8fPUWscC0RtjsWGw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", + "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", "dev": true, - "peer": true, "dependencies": { "lodash": "^4.17.20", - "renderkid": "^2.0.4" + "renderkid": "^3.0.0" } }, "node_modules/pretty-format": { @@ -25698,7 +25555,6 @@ "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", "dev": true, - "peer": true, "engines": { "node": ">= 0.10" } @@ -25819,40 +25675,16 @@ "dev": true }, "node_modules/renderkid": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.7.tgz", - "integrity": "sha512-oCcFyxaMrKsKcTY59qnCAtmDVSLfPbrv6A3tVbPdFMMrv5jaK10V6m40cKsoPNhAqN6rmHW9sswW4o3ruSrwUQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", + "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", "dev": true, - "peer": true, "dependencies": { "css-select": "^4.1.3", "dom-converter": "^0.2.0", "htmlparser2": "^6.1.0", "lodash": "^4.17.21", - "strip-ansi": "^3.0.1" - } - }, - "node_modules/renderkid/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/renderkid/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "peer": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" + "strip-ansi": "^6.0.1" } }, "node_modules/repeat-element": { @@ -28924,11 +28756,9 @@ "dev": true }, "node_modules/tapable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", - "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", - "dev": true, - "peer": true, + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", "engines": { "node": ">=6" } @@ -29121,21 +28951,20 @@ } }, "node_modules/terser": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz", - "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==", - "dev": true, - "peer": true, + "version": "5.12.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.12.1.tgz", + "integrity": "sha512-NXbs+7nisos5E+yXwAD+y7zrcTkMqb0dEJxIGtSKPdCBzopf7ni4odPul2aechpV7EXNvOudYOX2bb5tln1jbQ==", "dependencies": { + "acorn": "^8.5.0", "commander": "^2.20.0", - "source-map": "~0.6.1", - "source-map-support": "~0.5.12" + "source-map": "~0.7.2", + "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" }, "engines": { - "node": ">=6.0.0" + "node": ">=10" } }, "node_modules/terser-webpack-plugin": { @@ -29171,7 +29000,15 @@ } } }, - "node_modules/terser-webpack-plugin/node_modules/acorn": { + "node_modules/terser-webpack-plugin/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/terser/node_modules/acorn": { "version": "8.7.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", @@ -29182,37 +29019,12 @@ "node": ">=0.4.0" } }, - "node_modules/terser-webpack-plugin/node_modules/commander": { + "node_modules/terser/node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" }, - "node_modules/terser-webpack-plugin/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/terser-webpack-plugin/node_modules/terser": { - "version": "5.12.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.12.1.tgz", - "integrity": "sha512-NXbs+7nisos5E+yXwAD+y7zrcTkMqb0dEJxIGtSKPdCBzopf7ni4odPul2aechpV7EXNvOudYOX2bb5tln1jbQ==", - "dependencies": { - "acorn": "^8.5.0", - "commander": "^2.20.0", - "source-map": "~0.7.2", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser-webpack-plugin/node_modules/terser/node_modules/source-map": { + "node_modules/terser/node_modules/source-map": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", @@ -29220,23 +29032,6 @@ "node": ">= 8" } }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "peer": true - }, - "node_modules/terser/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -30511,8 +30306,7 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=", - "dev": true, - "peer": true + "dev": true }, "node_modules/utils-flatten": { "version": "1.0.0", @@ -31274,14 +31068,6 @@ "node": ">=0.8.x" } }, - "node_modules/webpack/node_modules/tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "engines": { - "node": ">=6" - } - }, "node_modules/websocket-driver": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", @@ -35102,6 +34888,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", @@ -35204,6 +34991,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", @@ -35250,6 +35038,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", @@ -36189,6 +35978,14 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "services/web/node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "engines": { + "node": ">=0.8.x" + } + }, "services/web/node_modules/expose-loader": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/expose-loader/-/expose-loader-3.1.0.tgz", @@ -37526,15 +37323,6 @@ "node": ">=10.13.0" } }, - "services/web/node_modules/tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "services/web/node_modules/toidentifier": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", @@ -43173,6 +42961,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", @@ -43252,6 +43041,7 @@ "eslint-plugin-react": "^7.27.0", "eslint-plugin-react-hooks": "^4.3.0", "eslint-plugin-standard": "^5.0.0", + "events": "^3.3.0", "expose-loader": "^3.1.0", "express": "4.17.1", "express-bearer-token": "^2.4.0", @@ -43266,6 +43056,7 @@ "handlebars": "^4.7.7", "handlebars-loader": "^1.7.1", "helmet": "^3.22.0", + "html-webpack-plugin": "^5.5.0", "http-proxy": "^1.18.1", "i18next": "^19.6.3", "i18next-fs-backend": "^1.0.7", @@ -44026,6 +43817,11 @@ } } }, + "events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" + }, "expose-loader": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/expose-loader/-/expose-loader-3.1.0.tgz", @@ -44962,12 +44758,6 @@ "stable": "^0.1.8" } }, - "tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true - }, "toidentifier": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", @@ -45789,6 +45579,12 @@ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==" }, + "@types/events": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", + "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==", + "dev": true + }, "@types/express": { "version": "4.17.13", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", @@ -45847,11 +45643,10 @@ } }, "@types/html-minifier-terser": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.2.tgz", - "integrity": "sha512-h4lTMgMJctJybDp8CQrxTUiiYmedihHWkjnF/8Pxseu2S6Nlfcy8kwboQ8yejh456rP2yWoEVm1sS/FVsfM48w==", - "dev": true, - "peer": true + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", + "dev": true }, "@types/http-proxy": { "version": "1.17.8", @@ -46154,13 +45949,6 @@ "@types/node": "*" } }, - "@types/source-list-map": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", - "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==", - "dev": true, - "peer": true - }, "@types/superagent": { "version": "3.8.7", "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-3.8.7.tgz", @@ -46171,87 +45959,16 @@ "@types/node": "*" } }, - "@types/tapable": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.8.tgz", - "integrity": "sha512-ipixuVrh2OdNmauvtT51o3d8z12p6LtFW9in7U79der/kwejjdNchQC5UMn5u/KxNoM7VHHOs/l8KS8uHxhODQ==", - "dev": true, - "peer": true - }, "@types/tough-cookie": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.1.tgz", "integrity": "sha512-Y0K95ThC3esLEYD6ZuqNek29lNX2EM1qxV8y2FTLUB0ff5wWrk7az+mLrnNFUnaXcgKye22+sFBRXOgpPILZNg==" }, - "@types/uglify-js": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.13.1.tgz", - "integrity": "sha512-O3MmRAk6ZuAKa9CHgg0Pr0+lUOqoMLpc9AS4R8ano2auvsg7IE8syF3Xh/NPr26TWklxYcqoEEFdzLLs1fV9PQ==", - "dev": true, - "peer": true, - "requires": { - "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "peer": true - } - } - }, "@types/underscore": { "version": "1.11.4", "resolved": "https://registry.npmjs.org/@types/underscore/-/underscore-1.11.4.tgz", "integrity": "sha512-uO4CD2ELOjw8tasUrAhvnn2W4A0ZECOvMjCivJr4gA9pGgjv+qxKWY9GLTMVEK8ej85BxQOocUyE7hImmSQYcg==" }, - "@types/webpack": { - "version": "4.41.32", - "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.32.tgz", - "integrity": "sha512-cb+0ioil/7oz5//7tZUSwbrSAN/NWHrQylz5cW8G0dWTcF/g+/dSdMlKVZspBYuMAN1+WnwHrkxiRrLcwd0Heg==", - "dev": true, - "peer": true, - "requires": { - "@types/node": "*", - "@types/tapable": "^1", - "@types/uglify-js": "*", - "@types/webpack-sources": "*", - "anymatch": "^3.0.0", - "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "peer": true - } - } - }, - "@types/webpack-sources": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-3.2.0.tgz", - "integrity": "sha512-Ft7YH3lEVRQ6ls8k4Ff1oB4jN6oy/XmU6tQISKdhfh+1mR+viZFphS6WL0IrtDOzvefmJg5a0s7ZQoRXwqTEFg==", - "dev": true, - "peer": true, - "requires": { - "@types/node": "*", - "@types/source-list-map": "*", - "source-map": "^0.7.3" - }, - "dependencies": { - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true, - "peer": true - } - } - }, "@types/ws": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-6.0.4.tgz", @@ -48492,7 +48209,6 @@ "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", "dev": true, - "peer": true, "requires": { "pascal-case": "^3.1.2", "tslib": "^2.0.3" @@ -48822,11 +48538,10 @@ "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==" }, "clean-css": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.4.tgz", - "integrity": "sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.0.tgz", + "integrity": "sha512-YYuuxv4H/iNb1Z/5IbMRoxgrzjWGhOEFfd+groZ5dMCVkpENiMZmwspdrzBo9286JjM1gZJPAyL7ZIdzuvu2AQ==", "dev": true, - "peer": true, "requires": { "source-map": "~0.6.0" }, @@ -48835,8 +48550,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "peer": true + "dev": true } } }, @@ -50687,7 +50401,6 @@ "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", "dev": true, - "peer": true, "requires": { "utila": "~0.4" } @@ -50769,7 +50482,6 @@ "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", "dev": true, - "peer": true, "requires": { "no-case": "^3.0.4", "tslib": "^2.0.3" @@ -51105,13 +50817,6 @@ "requires": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" - }, - "dependencies": { - "tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==" - } } }, "enquirer": { @@ -54335,19 +54040,26 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==" }, "html-minifier-terser": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz", - "integrity": "sha512-ZPr5MNObqnV/T9akshPKbVgyOqLmy+Bxo7juKCfTfnjNniTAMdy4hz21YQqoofMBJD2kdREaqPPdThoR78Tgxg==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", "dev": true, - "peer": true, "requires": { - "camel-case": "^4.1.1", - "clean-css": "^4.2.3", - "commander": "^4.1.1", + "camel-case": "^4.1.2", + "clean-css": "^5.2.2", + "commander": "^8.3.0", "he": "^1.2.0", - "param-case": "^3.0.3", + "param-case": "^3.0.4", "relateurl": "^0.2.7", - "terser": "^4.6.3" + "terser": "^5.10.0" + }, + "dependencies": { + "commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true + } } }, "html-parse-stringify": { @@ -54366,70 +54078,16 @@ } }, "html-webpack-plugin": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-4.5.2.tgz", - "integrity": "sha512-q5oYdzjKUIPQVjOosjgvCHQOv9Ett9CYYHlgvJeXG0qQvdSojnBq4vAdQBwn1+yGveAwHCoe/rMR86ozX3+c2A==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz", + "integrity": "sha512-sy88PC2cRTVxvETRgUHFrL4No3UxvcH8G1NepGhqaTT+GXN2kTamqasot0inS5hXeg1cMbFDt27zzo9p35lZVw==", "dev": true, - "peer": true, "requires": { - "@types/html-minifier-terser": "^5.0.0", - "@types/tapable": "^1.0.5", - "@types/webpack": "^4.41.8", - "html-minifier-terser": "^5.0.1", - "loader-utils": "^1.2.3", - "lodash": "^4.17.20", - "pretty-error": "^2.1.1", - "tapable": "^1.1.3", - "util.promisify": "1.0.0" - }, - "dependencies": { - "big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true, - "peer": true - }, - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "peer": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "peer": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - } - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true, - "peer": true - }, - "util.promisify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", - "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", - "dev": true, - "peer": true, - "requires": { - "define-properties": "^1.1.2", - "object.getownpropertydescriptors": "^2.0.3" - } - } + "@types/html-minifier-terser": "^6.0.0", + "html-minifier-terser": "^6.0.2", + "lodash": "^4.17.21", + "pretty-error": "^4.0.0", + "tapable": "^2.0.0" } }, "htmlparser2": { @@ -57460,7 +57118,6 @@ "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", "dev": true, - "peer": true, "requires": { "tslib": "^2.0.3" } @@ -58728,7 +58385,6 @@ "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", "dev": true, - "peer": true, "requires": { "lower-case": "^2.0.2", "tslib": "^2.0.3" @@ -60370,7 +60026,6 @@ "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", "dev": true, - "peer": true, "requires": { "dot-case": "^3.0.4", "tslib": "^2.0.3" @@ -60460,7 +60115,6 @@ "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", "dev": true, - "peer": true, "requires": { "no-case": "^3.0.4", "tslib": "^2.0.3" @@ -61031,14 +60685,13 @@ "dev": true }, "pretty-error": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.2.tgz", - "integrity": "sha512-EY5oDzmsX5wvuynAByrmY0P0hcp+QpnAKbJng2A2MPjVKXCxrDSUkzghVJ4ZGPIv+JC4gX8fPUWscC0RtjsWGw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", + "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", "dev": true, - "peer": true, "requires": { "lodash": "^4.17.20", - "renderkid": "^2.0.4" + "renderkid": "^3.0.0" } }, "pretty-format": { @@ -62199,8 +61852,7 @@ "version": "0.2.7", "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", - "dev": true, - "peer": true + "dev": true }, "release-zalgo": { "version": "1.0.0", @@ -62307,36 +61959,16 @@ "dev": true }, "renderkid": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.7.tgz", - "integrity": "sha512-oCcFyxaMrKsKcTY59qnCAtmDVSLfPbrv6A3tVbPdFMMrv5jaK10V6m40cKsoPNhAqN6rmHW9sswW4o3ruSrwUQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", + "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", "dev": true, - "peer": true, "requires": { "css-select": "^4.1.3", "dom-converter": "^0.2.0", "htmlparser2": "^6.1.0", "lodash": "^4.17.21", - "strip-ansi": "^3.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true, - "peer": true - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "peer": true, - "requires": { - "ansi-regex": "^2.0.0" - } - } + "strip-ansi": "^6.0.1" } }, "repeat-element": { @@ -64834,11 +64466,9 @@ "dev": true }, "tapable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", - "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", - "dev": true, - "peer": true + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==" }, "tar": { "version": "6.1.11", @@ -64986,30 +64616,30 @@ "dev": true }, "terser": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz", - "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==", - "dev": true, - "peer": true, + "version": "5.12.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.12.1.tgz", + "integrity": "sha512-NXbs+7nisos5E+yXwAD+y7zrcTkMqb0dEJxIGtSKPdCBzopf7ni4odPul2aechpV7EXNvOudYOX2bb5tln1jbQ==", "requires": { + "acorn": "^8.5.0", "commander": "^2.20.0", - "source-map": "~0.6.1", - "source-map-support": "~0.5.12" + "source-map": "~0.7.2", + "source-map-support": "~0.5.20" }, "dependencies": { + "acorn": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==" + }, "commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "peer": true + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" }, "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "peer": true + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==" } } }, @@ -65025,38 +64655,10 @@ "terser": "^5.7.2" }, "dependencies": { - "acorn": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==" - }, - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" - }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "terser": { - "version": "5.12.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.12.1.tgz", - "integrity": "sha512-NXbs+7nisos5E+yXwAD+y7zrcTkMqb0dEJxIGtSKPdCBzopf7ni4odPul2aechpV7EXNvOudYOX2bb5tln1jbQ==", - "requires": { - "acorn": "^8.5.0", - "commander": "^2.20.0", - "source-map": "~0.7.2", - "source-map-support": "~0.5.20" - }, - "dependencies": { - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==" - } - } } } }, @@ -66107,8 +65709,7 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=", - "dev": true, - "peer": true + "dev": true }, "utils-flatten": { "version": "1.0.0", @@ -66497,11 +66098,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" - }, - "tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==" } } }, diff --git a/services/web/.eslintrc b/services/web/.eslintrc index 5749fffc9e..74eb48f119 100644 --- a/services/web/.eslintrc +++ b/services/web/.eslintrc @@ -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" ] diff --git a/services/web/.gitignore b/services/web/.gitignore index 31c533d1ea..061e7d6bc2 100644 --- a/services/web/.gitignore +++ b/services/web/.gitignore @@ -91,3 +91,5 @@ cypress/downloads/ # Ace themes for conversion modules/source-editor/frontend/js/themes/ace/ + +!**/fixtures/**/*.log diff --git a/services/web/cypress.json b/services/web/cypress.json index 9fc6f1108a..ac49706665 100644 --- a/services/web/cypress.json +++ b/services/web/cypress.json @@ -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 diff --git a/services/web/test/frontend/features/pdf-preview/fixtures/test-example-2.pdf b/services/web/cypress/fixtures/build/output-2.pdf similarity index 100% rename from services/web/test/frontend/features/pdf-preview/fixtures/test-example-2.pdf rename to services/web/cypress/fixtures/build/output-2.pdf diff --git a/services/web/test/frontend/features/pdf-preview/fixtures/test-example-corrupt.pdf b/services/web/cypress/fixtures/build/output-corrupt.pdf similarity index 100% rename from services/web/test/frontend/features/pdf-preview/fixtures/test-example-corrupt.pdf rename to services/web/cypress/fixtures/build/output-corrupt.pdf diff --git a/services/web/cypress/fixtures/build/output-human-readable.log b/services/web/cypress/fixtures/build/output-human-readable.log new file mode 100644 index 0000000000..af776aa2d3 --- /dev/null +++ b/services/web/cypress/fixtures/build/output-human-readable.log @@ -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. + + ) diff --git a/services/web/cypress/fixtures/build/output-undefined-references.log b/services/web/cypress/fixtures/build/output-undefined-references.log new file mode 100644 index 0000000000..c2e452138c --- /dev/null +++ b/services/web/cypress/fixtures/build/output-undefined-references.log @@ -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. +) diff --git a/services/web/cypress/fixtures/build/output.blg b/services/web/cypress/fixtures/build/output.blg new file mode 100644 index 0000000000..dd54e2034c --- /dev/null +++ b/services/web/cypress/fixtures/build/output.blg @@ -0,0 +1 @@ +This is BibTeX, Version 4.0 diff --git a/services/web/cypress/fixtures/build/output.log b/services/web/cypress/fixtures/build/output.log new file mode 100644 index 0000000000..768a90556f --- /dev/null +++ b/services/web/cypress/fixtures/build/output.log @@ -0,0 +1,19 @@ +The LaTeX compiler output + * With a lot of details + +Wrapped in an HTML
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.
+ \Zlpha
+
+ main.tex, line 23
diff --git a/services/web/test/frontend/features/pdf-preview/fixtures/test-example.pdf b/services/web/cypress/fixtures/build/output.pdf
similarity index 100%
rename from services/web/test/frontend/features/pdf-preview/fixtures/test-example.pdf
rename to services/web/cypress/fixtures/build/output.pdf
diff --git a/services/web/cypress/plugins/index.js b/services/web/cypress/plugins/index.js
index 06a2eec53c..e31a2a6aaa 100644
--- a/services/web/cypress/plugins/index.js
+++ b/services/web/cypress/plugins/index.js
@@ -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 })
})
diff --git a/services/web/cypress/support/ct/index.js b/services/web/cypress/support/ct/index.js
deleted file mode 100644
index ea50299e96..0000000000
--- a/services/web/cypress/support/ct/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-require('../../../frontend/stylesheets/style.less')
-require('../shared/exceptions')
-require('./i18n')
diff --git a/services/web/cypress/support/ct/index.ts b/services/web/cypress/support/ct/index.ts
new file mode 100644
index 0000000000..c660b651b0
--- /dev/null
+++ b/services/web/cypress/support/ct/index.ts
@@ -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'
diff --git a/services/web/cypress/support/ct/i18n.js b/services/web/cypress/support/ct/window.ts
similarity index 70%
rename from services/web/cypress/support/ct/i18n.js
rename to services/web/cypress/support/ct/window.ts
index f61d91e56a..82b972bf27 100644
--- a/services/web/cypress/support/ct/i18n.js
+++ b/services/web/cypress/support/ct/window.ts
@@ -1,4 +1,2 @@
window.i18n = { currentLangCode: 'en' }
window.ExposedSettings = { appName: 'Overleaf' }
-
-require('../../../frontend/js/i18n')
diff --git a/services/web/cypress/support/shared/commands.ts b/services/web/cypress/support/shared/commands.ts
new file mode 100644
index 0000000000..546e07f648
--- /dev/null
+++ b/services/web/cypress/support/shared/commands.ts
@@ -0,0 +1,3 @@
+import '@testing-library/cypress/add-commands'
+import './compile'
+import './events'
diff --git a/services/web/cypress/support/shared/compile.ts b/services/web/cypress/support/shared/compile.ts
new file mode 100644
index 0000000000..c4be3ca455
--- /dev/null
+++ b/services/web/cypress/support/shared/compile.ts
@@ -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`)
+})
diff --git a/services/web/cypress/support/shared/events.ts b/services/web/cypress/support/shared/events.ts
new file mode 100644
index 0000000000..46987e1612
--- /dev/null
+++ b/services/web/cypress/support/shared/events.ts
@@ -0,0 +1,5 @@
+Cypress.Commands.add('interceptEvents', () => {
+ cy.intercept('POST', '/event/*', {
+ statusCode: 204,
+ })
+})
diff --git a/services/web/cypress/support/shared/exceptions.js b/services/web/cypress/support/shared/exceptions.ts
similarity index 100%
rename from services/web/cypress/support/shared/exceptions.js
rename to services/web/cypress/support/shared/exceptions.ts
diff --git a/services/web/cypress/support/shared/types.d.ts b/services/web/cypress/support/shared/types.d.ts
new file mode 100644
index 0000000000..89a7b6a9aa
--- /dev/null
+++ b/services/web/cypress/support/shared/types.d.ts
@@ -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
+ }
+}
diff --git a/services/web/frontend/js/shared/components/beta-badge.js b/services/web/frontend/js/shared/components/beta-badge.js
index 2c4d8fa580..b05cb6f5bf 100644
--- a/services/web/frontend/js/shared/components/beta-badge.js
+++ b/services/web/frontend/js/shared/components/beta-badge.js
@@ -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,
diff --git a/services/web/frontend/js/utils/worker.js b/services/web/frontend/js/utils/worker.js
index 96b9fc73bd..28f161c37e 100644
--- a/services/web/frontend/js/utils/worker.js
+++ b/services/web/frontend/js/utils/worker.js
@@ -1,4 +1,7 @@
export const createWorker = callback => {
+ if (process.env.CYPRESS) {
+ return callback()
+ }
const webpackPublicPath = __webpack_public_path__
__webpack_public_path__ = '/'
callback()
diff --git a/services/web/package.json b/services/web/package.json
index 9b59e8eade..ada229d172 100644
--- a/services/web/package.json
+++ b/services/web/package.json
@@ -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",
diff --git a/services/web/test/frontend/components/pdf-preview/detach-compile-button.spec.tsx b/services/web/test/frontend/components/pdf-preview/detach-compile-button.spec.tsx
new file mode 100644
index 0000000000..8fd0fb26e0
--- /dev/null
+++ b/services/web/test/frontend/components/pdf-preview/detach-compile-button.spec.tsx
@@ -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(' ', 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(
+
+
+
+ )
+
+ 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(
+
+
+
+ ).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(
+
+
+
+ ).then(() => {
+ sysendTestHelper.receiveMessage({
+ role: 'detacher',
+ event: 'connected',
+ })
+ })
+
+ cy.findByRole('button', { name: 'Recompile' }).should('not.exist')
+ })
+})
diff --git a/services/web/test/frontend/components/pdf-preview/pdf-js-viewer.spec.tsx b/services/web/test/frontend/components/pdf-preview/pdf-js-viewer.spec.tsx
new file mode 100644
index 0000000000..c0f8a60b37
--- /dev/null
+++ b/services/web/test/frontend/components/pdf-preview/pdf-js-viewer.spec.tsx
@@ -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(' ', function () {
+ beforeEach(function () {
+ cy.interceptCompile()
+ cy.interceptEvents()
+ })
+
+ it('loads all PDF pages', function () {
+ const scope = mockScope()
+
+ mount(
+
+
+
+
+
+ )
+
+ 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(
+
+
+
+
+
+ )
+
+ cy.findByLabelText('Loading…')
+ })
+
+ it('can be unmounted while loading a document', function () {
+ const scope = mockScope()
+
+ mount(
+
+
+
+
+
+ )
+
+ unmount()
+ })
+
+ it('can be unmounted after loading a document', function () {
+ const scope = mockScope()
+
+ mount(
+
+
+
+
+
+ )
+
+ cy.findByLabelText('Page 1')
+
+ unmount()
+ })
+})
diff --git a/services/web/test/frontend/components/pdf-preview/pdf-logs-entries.spec.tsx b/services/web/test/frontend/components/pdf-preview/pdf-logs-entries.spec.tsx
new file mode 100644
index 0000000000..c07e2881c1
--- /dev/null
+++ b/services/web/test/frontend/components/pdf-preview/pdf-logs-entries.spec.tsx
@@ -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(' ', 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(
+
+
+
+ )
+
+ cy.contains('You have placed an alignment tab character')
+ })
+
+ it('opens doc on click', function () {
+ mount(
+
+
+
+ )
+
+ 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(
+
+
+
+ ).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(
+
+
+
+ )
+
+ 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,
+ },
+ ],
+ },
+ })
+ })
+ })
+})
diff --git a/services/web/test/frontend/components/pdf-preview/pdf-preview-detached-root.spec.tsx b/services/web/test/frontend/components/pdf-preview/pdf-preview-detached-root.spec.tsx
new file mode 100644
index 0000000000..b20d6a1e43
--- /dev/null
+++ b/services/web/test/frontend/components/pdf-preview/pdf-preview-detached-root.spec.tsx
@@ -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(' ', function () {
+ beforeEach(function () {
+ window.user = { id: 'user1' }
+
+ window.metaAttributesCache = new Map([
+ ['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( ).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( ).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: [],
+ },
+ })
+ })
+ })
+})
diff --git a/services/web/test/frontend/components/pdf-preview/pdf-preview-hybrid-toolbar.spec.tsx b/services/web/test/frontend/components/pdf-preview/pdf-preview-hybrid-toolbar.spec.tsx
new file mode 100644
index 0000000000..d8f6dfa35c
--- /dev/null
+++ b/services/web/test/frontend/components/pdf-preview/pdf-preview-hybrid-toolbar.spec.tsx
@@ -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(' ', function () {
+ beforeEach(function () {
+ cy.interceptCompile()
+ cy.interceptEvents()
+ })
+
+ afterEach(function () {
+ window.metaAttributesCache = new Map()
+ sysendTestHelper.resetHistory()
+ })
+
+ it('shows normal mode', function () {
+ mount(
+
+
+
+ )
+
+ 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(
+
+
+
+ )
+
+ 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(
+
+
+
+ ).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(
+
+
+
+ ).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(
+
+
+
+ )
+
+ cy.tick(6000)
+
+ cy.findByRole('button', { name: 'Redirect to editor' })
+ })
+ })
+})
diff --git a/services/web/test/frontend/components/pdf-preview/pdf-preview.spec.tsx b/services/web/test/frontend/components/pdf-preview/pdf-preview.spec.tsx
new file mode 100644
index 0000000000..8f20d0838d
--- /dev/null
+++ b/services/web/test/frontend/components/pdf-preview/pdf-preview.spec.tsx
@@ -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(' ', function () {
+ beforeEach(function () {
+ cy.interceptCompile()
+ cy.interceptEvents()
+ })
+
+ it('renders the PDF preview', function () {
+ const scope = mockScope()
+
+ mount(
+
+
+
+
+
+ )
+
+ // wait for "compile on load" to finish
+ cy.findByRole('button', { name: 'Compiling…' })
+ cy.wait('@compile')
+ cy.findByRole('button', { name: 'Recompile' })
+ cy.wait('@compile-pdf')
+ })
+
+ it('runs a compile when the Recompile button is pressed', function () {
+ const scope = mockScope()
+
+ mount(
+
+
+
+
+
+ )
+
+ // wait for "compile on load" to finish
+ cy.findByRole('button', { name: 'Compiling…' })
+ cy.wait('@compile')
+ cy.wait('@compile-pdf')
+
+ cy.interceptCompile('recompile')
+
+ // press the Recompile button => compile
+ cy.findByRole('button', { name: 'Recompile' }).click()
+
+ // wait for "recompile" to finish
+ // cy.findByRole('button', { name: 'Compiling…' })
+ cy.wait('@recompile-pdf')
+ cy.wait('@recompile-log')
+ cy.wait('@recompile-blg')
+
+ cy.findByRole('button', { name: 'Recompile' })
+
+ cy.contains('Your Paper')
+ })
+
+ it('runs a compile on `pdf:recompile` event', function () {
+ const scope = mockScope()
+
+ mount(
+
+
+
+
+
+ )
+
+ // wait for "compile on load" to finish
+ cy.findByRole('button', { name: 'Compiling…' })
+ cy.wait('@compile')
+ cy.wait('@compile-pdf')
+
+ cy.interceptCompile('recompile')
+
+ cy.window().then(win => {
+ win.dispatchEvent(new CustomEvent('pdf:recompile'))
+ })
+
+ // wait for "recompile" to finish
+ // cy.findByRole('button', { name: 'Compiling…' })
+ cy.wait('@recompile')
+
+ cy.findByRole('button', { name: 'Recompile' })
+
+ cy.wait('@recompile-pdf')
+ cy.contains('Your Paper')
+ })
+
+ it('does not compile while compiling', function () {
+ let compileResolve
+ 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(
+
+
+
+
+
+ ).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(
+
+
+
+
+
+ )
+
+ 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(
+
+
+
+
+
+ )
+
+ // 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(
+
+
+
+
+
+ )
+
+ // 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(
+
+
+
+
+
+ )
+
+ // 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(
+
+
+
+
+
+ )
+
+ // 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(
+
+
+
+
+
+ )
+
+ // 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(
+
+
+
+
+
+ )
+
+ // 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(
+
+
+
+
+
+ )
+
+ // 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(
+
+
+
+
+
+ )
+
+ // 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(
+
+
+
+
+
+ )
+
+ 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(
+
+
+
+
+
+ )
+
+ 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(
+
+
+
+
+
+ )
+
+ 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(
+
+
+
+
+
+ )
+
+ 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')
+ })
+ })
+})
diff --git a/services/web/test/frontend/components/pdf-preview/pdf-synctex-controls.spec.tsx b/services/web/test/frontend/components/pdf-preview/pdf-synctex-controls.spec.tsx
new file mode 100644
index 0000000000..62d642d27a
--- /dev/null
+++ b/services/web/test/frontend/components/pdf-preview/pdf-synctex-controls.spec.tsx
@@ -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(' ', 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(
+
+
+
+
+
+ )
+
+ 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(
+
+
+
+
+
+ )
+
+ 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(
+
+
+
+
+
+ )
+
+ 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(
+
+
+
+
+
+ )
+
+ 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(
+
+
+
+
+
+ ).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(
+
+
+
+
+
+ )
+
+ 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(
+
+
+
+
+ )
+
+ 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(
+
+
+
+
+ )
+
+ 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(
+
+
+
+
+ ).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')
+ })
+ })
+})
diff --git a/services/web/test/frontend/components/pdf-preview/scope.tsx b/services/web/test/frontend/components/pdf-preview/scope.tsx
new file mode 100644
index 0000000000..ca60f9970e
--- /dev/null
+++ b/services/web/test/frontend/components/pdf-preview/scope.tsx
@@ -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,
+})
diff --git a/services/web/test/frontend/components/shared/beta-badge.spec.js b/services/web/test/frontend/components/shared/beta-badge.spec.tsx
similarity index 100%
rename from services/web/test/frontend/components/shared/beta-badge.spec.js
rename to services/web/test/frontend/components/shared/beta-badge.spec.tsx
diff --git a/services/web/test/frontend/features/file-tree/components/file-tree-item/file-tree-item-inner.test.js b/services/web/test/frontend/features/file-tree/components/file-tree-item/file-tree-item-inner.test.js
index f3828e42a1..334d755154 100644
--- a/services/web/test/frontend/features/file-tree/components/file-tree-item/file-tree-item-inner.test.js
+++ b/services/web/test/frontend/features/file-tree/components/file-tree-item/file-tree-item-inner.test.js
@@ -9,13 +9,8 @@ import FileTreeContextMenu from '../../../../../../frontend/js/features/file-tre
describe(' ', function () {
const setContextMenuCoords = sinon.stub()
- beforeEach(function () {
- global.requestAnimationFrame = sinon.stub()
- })
-
afterEach(function () {
setContextMenuCoords.reset()
- delete global.requestAnimationFrame
})
describe('menu', function () {
diff --git a/services/web/test/frontend/features/file-tree/components/file-tree-root.test.js b/services/web/test/frontend/features/file-tree/components/file-tree-root.test.js
index 11c498191a..e9e94f01b0 100644
--- a/services/web/test/frontend/features/file-tree/components/file-tree-root.test.js
+++ b/services/web/test/frontend/features/file-tree/components/file-tree-root.test.js
@@ -14,13 +14,11 @@ describe(' ', 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()
diff --git a/services/web/test/frontend/features/file-tree/flows/create-folder.test.js b/services/web/test/frontend/features/file-tree/flows/create-folder.test.js
index e46a86b3ca..1cc5f0eb19 100644
--- a/services/web/test/frontend/features/file-tree/flows/create-folder.test.js
+++ b/services/web/test/frontend/features/file-tree/flows/create-folder.test.js
@@ -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()
diff --git a/services/web/test/frontend/features/file-tree/flows/rename-entity.test.js b/services/web/test/frontend/features/file-tree/flows/rename-entity.test.js
index 9c2fd64c0d..431b1efeef 100644
--- a/services/web/test/frontend/features/file-tree/flows/rename-entity.test.js
+++ b/services/web/test/frontend/features/file-tree/flows/rename-entity.test.js
@@ -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()
diff --git a/services/web/test/frontend/features/pdf-preview/components/detach-compile-button.test.js b/services/web/test/frontend/features/pdf-preview/components/detach-compile-button.test.js
deleted file mode 100644
index f2c5ac4b50..0000000000
--- a/services/web/test/frontend/features/pdf-preview/components/detach-compile-button.test.js
+++ /dev/null
@@ -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(' ', 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( )
- 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( )
-
- 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( )
- sysendTestHelper.receiveMessage({
- role: 'detacher',
- event: 'connected',
- })
-
- expect(
- await screen.queryByRole('button', {
- name: 'Recompile',
- })
- ).to.not.exist
- })
-})
diff --git a/services/web/test/frontend/features/pdf-preview/components/pdf-js-viewer.test.js b/services/web/test/frontend/features/pdf-preview/components/pdf-js-viewer.test.js
deleted file mode 100644
index 03d146a7bb..0000000000
--- a/services/web/test/frontend/features/pdf-preview/components/pdf-js-viewer.test.js
+++ /dev/null
@@ -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(' ', function () {
- it('loads all PDF pages', async function () {
- renderWithEditorContext( )
-
- 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( )
- await screen.findByLabelText('Loading…')
- })
-
- it('can be unmounted while loading a document', async function () {
- const { unmount } = renderWithEditorContext( )
- unmount()
- })
-
- it('can be unmounted after loading a document', async function () {
- const { unmount } = renderWithEditorContext( )
- await screen.findByLabelText('Page 1')
- unmount()
- })
-})
diff --git a/services/web/test/frontend/features/pdf-preview/components/pdf-logs-entries.test.js b/services/web/test/frontend/features/pdf-preview/components/pdf-logs-entries.test.js
deleted file mode 100644
index 51796f6275..0000000000
--- a/services/web/test/frontend/features/pdf-preview/components/pdf-logs-entries.test.js
+++ /dev/null
@@ -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(' ', 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( , {
- fileTreeManager,
- editorManager,
- })
- screen.getByText(/You have placed an alignment tab character/)
- })
-
- it('opens doc on click', async function () {
- renderWithEditorContext( , {
- 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( , {
- 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( , {
- 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,
- },
- ],
- },
- })
- })
-})
diff --git a/services/web/test/frontend/features/pdf-preview/components/pdf-preview-detached-root.test.js b/services/web/test/frontend/features/pdf-preview/components/pdf-preview-detached-root.test.js
deleted file mode 100644
index 6af6d9ca32..0000000000
--- a/services/web/test/frontend/features/pdf-preview/components/pdf-preview-detached-root.test.js
+++ /dev/null
@@ -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(' ', 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( )
-
- 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( )
-
- 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: [],
- },
- })
- })
-})
diff --git a/services/web/test/frontend/features/pdf-preview/components/pdf-preview-hybrid-toolbar.test.js b/services/web/test/frontend/features/pdf-preview/components/pdf-preview-hybrid-toolbar.test.js
deleted file mode 100644
index 6167bd1a10..0000000000
--- a/services/web/test/frontend/features/pdf-preview/components/pdf-preview-hybrid-toolbar.test.js
+++ /dev/null
@@ -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(' ', 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( )
-
- 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( )
-
- await screen.getByText(/Connecting with the editor/)
- })
-
- it('shows compile UI when connected', async function () {
- window.metaAttributesCache.set('ol-detachRole', 'detached')
- renderWithEditorContext( )
- 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( )
- 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( )
- clock.tick(6000)
-
- await screen.getByRole('button', {
- name: 'Redirect to editor',
- })
- })
- })
-})
diff --git a/services/web/test/frontend/features/pdf-preview/components/pdf-preview.test.js b/services/web/test/frontend/features/pdf-preview/components/pdf-preview.test.js
deleted file mode 100644
index 501c6d4b6d..0000000000
--- a/services/web/test/frontend/features/pdf-preview/components/pdf-preview.test.js
+++ /dev/null
@@ -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(' ', 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( , { 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( , { 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( , { 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( , { 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( , { 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( , { 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( , { 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( , {
- 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( , { 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( , { 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( , { 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( , { 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( , { 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( , { 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( , { 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( , { 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( , { 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
- })
- })
-})
diff --git a/services/web/test/frontend/features/pdf-preview/components/pdf-synctex-controls.test.js b/services/web/test/frontend/features/pdf-preview/components/pdf-synctex-controls.test.js
deleted file mode 100644
index aa91463237..0000000000
--- a/services/web/test/frontend/features/pdf-preview/components/pdf-synctex-controls.test.js
+++ /dev/null
@@ -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(' ', 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(
- <>
-
-
-
- >,
- { 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(
- <>
-
-
-
- >,
- { 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(
- <>
-
-
-
- >,
- { 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(
- <>
-
-
-
- >,
- { 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(
- <>
-
-
-
- >,
- { 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(
- <>
-
-
-
- >,
- { 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(
- <>
-
-
- >,
- { 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(
- <>
-
-
- >,
- { 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(
- <>
-
-
- >,
- { 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
- )
- })
- })
-})
diff --git a/services/web/test/frontend/features/pdf-preview/utils/mock-compile.js b/services/web/test/frontend/features/pdf-preview/utils/mock-compile.js
deleted file mode 100644
index 55d78820d4..0000000000
--- a/services/web/test/frontend/features/pdf-preview/utils/mock-compile.js
+++ /dev/null
@@ -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 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.
- \\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
- })
-}
diff --git a/services/web/test/frontend/helpers/editor-providers.js b/services/web/test/frontend/helpers/editor-providers.js
index 40878b87ea..f039bebc24 100644
--- a/services/web/test/frontend/helpers/editor-providers.js
+++ b/services/web/test/frontend/helpers/editor-providers.js
@@ -28,7 +28,7 @@ export function EditorProviders({
},
isRestrictedTokenMember = false,
clsiServerId = '1234',
- scope,
+ scope = {},
features = {
referencesSearch: true,
},
diff --git a/services/web/tsconfig.json b/services/web/tsconfig.json
index 098dd4cde8..5b033da62b 100644
--- a/services/web/tsconfig.json
+++ b/services/web/tsconfig.json
@@ -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"
]
}
diff --git a/services/web/types/window.ts b/services/web/types/window.ts
index 36972f87da..5667e84da2 100644
--- a/services/web/types/window.ts
+++ b/services/web/types/window.ts
@@ -5,6 +5,11 @@ declare global {
user: {
id: string
}
+ metaAttributesCache: Map
+ i18n: {
+ currentLangCode: string
+ }
+ ExposedSettings: Record
}
}
export {} // pretend this is a module
diff --git a/services/web/webpack.config.js b/services/web/webpack.config.js
index 0b12c6058a..3a6462b6fa 100644
--- a/services/web/webpack.config.js
+++ b/services/web/webpack.config.js
@@ -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