diff --git a/histomicsui/web_client/histomicsui.ts b/histomicsui/web_client/histomicsui.ts index c76bc47c..f9c54dea 100644 --- a/histomicsui/web_client/histomicsui.ts +++ b/histomicsui/web_client/histomicsui.ts @@ -1,4 +1,4 @@ -import { girder } from '@girder/core'; +import {girder} from '@girder/core'; declare global { interface Window { @@ -12,40 +12,39 @@ interface StaticFilesSpec { js: string[], } - const initializeHistomicsApp = async (apiRoot: string, el: string | HTMLElement = 'body') => { - return new Promise((resolve, reject) => { - $(() => { - girder.rest.setApiRoot(apiRoot); - girder.router.enabled(false); - girder.events.trigger('g:appload.before'); - girder.rest.restRequest({ - url: 'system/setting/histomicsui', - method: 'GET', - }).done(async (resp: any) => { - // We use dynamic import because this app's code depends on other plugins' - // code having been loaded under `girder.plugins.xxx` at import time. - const App = (await import('./app') as any).default; - const app = new App({ - el, - parentView: null, - brandName: resp['histomicsui.brand_name'], - brandColor: resp['histomicsui.brand_color'], - bannerColor: resp['histomicsui.banner_color'], - helpURL: resp['histomicsui.help_url'], - helpTooltip: resp['histomicsui.help_tooltip'], - helpText: resp['histomicsui.help_text'], - }); - document.title = resp['histomicsui.brand_name']; - app.bindRoutes(); - girder.events.trigger('g:appload.after', app); - resolve(app); - }).fail((resp: any) => { - girder.events.trigger('g:error', resp); - reject(new Error("Could not retrieve public settings from server.")); - }); - }); - }); + return new Promise((resolve, reject) => { + $(() => { + girder.rest.setApiRoot(apiRoot); + girder.router.enabled(false); + girder.events.trigger('g:appload.before'); + girder.rest.restRequest({ + url: 'system/setting/histomicsui', + method: 'GET' + }).done(async (resp: any) => { + // We use dynamic import because this app's code depends on other plugins' + // code having been loaded under `girder.plugins.xxx` at import time. + const App = (await import('./app') as any).default; + const app = new App({ + el, + parentView: null, + brandName: resp['histomicsui.brand_name'], + brandColor: resp['histomicsui.brand_color'], + bannerColor: resp['histomicsui.banner_color'], + helpURL: resp['histomicsui.help_url'], + helpTooltip: resp['histomicsui.help_tooltip'], + helpText: resp['histomicsui.help_text'] + }); + document.title = resp['histomicsui.brand_name']; + app.bindRoutes(); + girder.events.trigger('g:appload.after', app); + resolve(app); + }).fail((resp: any) => { + girder.events.trigger('g:error', resp); + reject(new Error('Could not retrieve public settings from server.')); + }); + }); + }); }; const appElement = document.getElementById('app'); @@ -56,33 +55,33 @@ if (!apiRoot.startsWith('/') && apiRoot.indexOf(':') < 0) { } (async () => { - let origin = apiRoot.startsWith('/') ? window.origin : new URL(apiRoot).origin; - origin += apiRoot.includes('/api/') ? apiRoot.substring(0, apiRoot.indexOf('/api/')) + '/' : ''; - const staticFilesResp = await fetch(`${apiRoot}/system/plugin_static_files`); - const staticFiles: StaticFilesSpec = await staticFilesResp.json(); + let origin = apiRoot.startsWith('/') ? window.origin : new URL(apiRoot).origin; + origin += apiRoot.includes('/api/') ? apiRoot.substring(0, apiRoot.indexOf('/api/')) + '/' : ''; + const staticFilesResp = await fetch(`${apiRoot}/system/plugin_static_files`); + const staticFiles: StaticFilesSpec = await staticFilesResp.json(); - staticFiles.css.forEach((href) => { - const link = document.createElement('link'); - link.rel = 'stylesheet'; - link.type = 'text/css'; - link.href = new URL(href, origin).href; - document.head.appendChild(link); - }); - - // Because some plugin code depends on other plugin code at import time, we can't - // await these in parallel. For now load them serially until the server is smart enough to - // provides a DAG. - for (const href of staticFiles.js) { - await new Promise((resolve) => { - const script = document.createElement('script'); - script.type = 'text/javascript'; - script.src = new URL(href, origin).href; - document.head.appendChild(script); - script.addEventListener('load', function() { - resolve(); - }); + staticFiles.css.forEach((href) => { + const link = document.createElement('link'); + link.rel = 'stylesheet'; + link.type = 'text/css'; + link.href = new URL(href, origin).href; + document.head.appendChild(link); }); - }; - await initializeHistomicsApp(apiRoot, document.getElementById('app')); + // Because some plugin code depends on other plugin code at import time, we can't + // await these in parallel. For now load them serially until the server is smart enough to + // provides a DAG. + for (const href of staticFiles.js) { + await new Promise((resolve) => { + const script = document.createElement('script'); + script.type = 'text/javascript'; + script.src = new URL(href, origin).href; + document.head.appendChild(script); + script.addEventListener('load', function () { + resolve(); + }); + }); + } + + await initializeHistomicsApp(apiRoot, document.getElementById('app')); })(); diff --git a/histomicsui/web_client/package-lock.json b/histomicsui/web_client/package-lock.json index 79349b74..f9826462 100644 --- a/histomicsui/web_client/package-lock.json +++ b/histomicsui/web_client/package-lock.json @@ -30,6 +30,8 @@ "@girder/eslint-config": "*", "@girder/pug-lint-config": "*", "@playwright/test": "^1.47.2", + "@typescript-eslint/eslint-plugin": "^8.62.1", + "@typescript-eslint/parser": "^8.62.1", "@vitejs/plugin-vue2": "^2.3.1", "concurrently": "^8.2.2", "eslint": "^8.20.0", @@ -38,6 +40,7 @@ "eslint-plugin-backbone": "^2.1.1", "eslint-plugin-import": "^2.25.3", "eslint-plugin-n": "^15.2.4", + "eslint-plugin-playwright": "^2.10.4", "eslint-plugin-promise": "^6.0.0", "eslint-plugin-vue": "~9.14.0", "npm-run-all": "^4.1.5", @@ -252,6 +255,7 @@ "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.38.tgz", "integrity": "sha512-vAMKHfImQlYSy0C+PBue4s3ERZ2xGKfgZg5GXAsLInq1dyh2H78ILVP5sK0KPFPVW4kv+OGCIvBEondcjpZp7A==", "license": "MIT", + "peer": true, "dependencies": { "@vue/compiler-dom": "3.5.38", "@vue/compiler-sfc": "3.5.38", @@ -340,7 +344,6 @@ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" @@ -351,7 +354,6 @@ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "license": "MIT", - "peer": true, "engines": { "node": ">=6.0.0" } @@ -361,7 +363,6 @@ "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" @@ -378,7 +379,6 @@ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -704,8 +704,7 @@ "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/json5": { "version": "0.0.29", @@ -719,11 +718,293 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.3.tgz", "integrity": "sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg==", "license": "MIT", - "peer": true, "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.62.1.tgz", + "integrity": "sha512-4EQM77WgVNxj7OkL/5b/D/xZsw00G577+UriYTC7JF5opcF3T2AuoeY7ueLaZgSVjSgCS6yOAJB5bRGLPSJUzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.62.1", + "@typescript-eslint/type-utils": "8.62.1", + "@typescript-eslint/utils": "8.62.1", + "@typescript-eslint/visitor-keys": "8.62.1", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.62.1", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.62.1.tgz", + "integrity": "sha512-sPhE4iHuJDSvoAiec+Ro8JyXw8f0ql13HFR82P99nCm9GwTEKG0KYLvDe6REk8BCXuit6vJAv/Yxg5ABaNS2rA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@typescript-eslint/scope-manager": "8.62.1", + "@typescript-eslint/types": "8.62.1", + "@typescript-eslint/typescript-estree": "8.62.1", + "@typescript-eslint/visitor-keys": "8.62.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.62.1.tgz", + "integrity": "sha512-yQ3RgY5RkSBpsNS1Bx/JQEcA24FOSdfGktoyprAr5u18390UQdtVcfnEv4nIrIshNnavlVyZBKxQwT1fIAE6cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.62.1", + "@typescript-eslint/types": "^8.62.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.62.1.tgz", + "integrity": "sha512-r4d249KbQ1SFdpeStvob8Ih6aPPIzfqllPVOtvhve6ZcpuVcYo5/7zUWckKpHE7StASX4kTKZTLf0WQm/wPkcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.62.1", + "@typescript-eslint/visitor-keys": "8.62.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.62.1.tgz", + "integrity": "sha512-xadytJqX9vJVQ2fdQjkcIVigwaOJNWkpjdLt6cEQ+xPnrI1fkp+/jZE/I97k9KUjqtpd25i0HeyZf3T6dutv2g==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.62.1.tgz", + "integrity": "sha512-aXM5xlqXiTxPibXB93cLAURfT3rlizf7uMXISCXy66Isr/9hISJx3yDsKl0L7lKa51b8JpFuNKby0/O0pEm9jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.62.1", + "@typescript-eslint/typescript-estree": "8.62.1", + "@typescript-eslint/utils": "8.62.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.62.1.tgz", + "integrity": "sha512-ooCzJFaf+Hg+uG6fA3NRFGuFjlfNlDhBthbv4ZPU/0elCAFUfnyXUvf/WOpHz/jYwSmvU2GkR2LtyUfy1AxZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.62.1.tgz", + "integrity": "sha512-xMcW9oP9u7fAMXYs9A65CVmtLQe2r//oXINHfi8HV+oiqhih17sbLdhXr4540YWlgpDKQdY854OL5ZrdCiQsAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.62.1", + "@typescript-eslint/tsconfig-utils": "8.62.1", + "@typescript-eslint/types": "8.62.1", + "@typescript-eslint/visitor-keys": "8.62.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", + "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.62.1.tgz", + "integrity": "sha512-sHtbPfuKNZCG+ih8SyjjucqRntSVmp8XgL5u6o9mAhiSn8ds5o/M/XdM0abweme2Tln3szOstOrZ9OXitvPh0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.62.1", + "@typescript-eslint/types": "8.62.1", + "@typescript-eslint/typescript-estree": "8.62.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.62.1.tgz", + "integrity": "sha512-4g3BLxfdTMy8iZG0MaBkadnlRrCJ74cQiFbyEVMrkwIoqdyaXXQM22cotDvrl4x28wgIZ9rEJRoM+mmhSJpJ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.62.1", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@ungap/structured-clone": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz", @@ -879,7 +1160,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/helper-numbers": "1.13.2", "@webassemblyjs/helper-wasm-bytecode": "1.13.2" @@ -889,29 +1169,25 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/helper-api-error": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/helper-buffer": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/helper-numbers": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.13.2", "@webassemblyjs/helper-api-error": "1.13.2", @@ -922,15 +1198,13 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/helper-wasm-section": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -943,7 +1217,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", "license": "MIT", - "peer": true, "dependencies": { "@xtuc/ieee754": "^1.2.0" } @@ -953,7 +1226,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@xtuc/long": "4.2.2" } @@ -962,15 +1234,13 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/wasm-edit": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -987,7 +1257,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", @@ -1001,7 +1270,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -1014,7 +1282,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-api-error": "1.13.2", @@ -1029,7 +1296,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" @@ -1039,15 +1305,13 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/@xtuc/long": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "license": "Apache-2.0", - "peer": true + "license": "Apache-2.0" }, "node_modules/abs-svg-path": { "version": "0.1.1", @@ -1066,6 +1330,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1078,7 +1343,6 @@ "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=10.13.0" }, @@ -1117,7 +1381,6 @@ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", "license": "MIT", - "peer": true, "dependencies": { "ajv": "^8.0.0" }, @@ -1135,7 +1398,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -1151,8 +1413,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/almost-equal": { "version": "1.1.0", @@ -1522,7 +1783,6 @@ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.37.tgz", "integrity": "sha512-girxaJ7WZssDOFhzCGZTDKoTa1gk6A1TbflaYTpykLJ4UU9Fz9kx1aREM8JCuoVHbL8X8T/mJg7w2oYSq72Oig==", "license": "Apache-2.0", - "peer": true, "bin": { "baseline-browser-mapping": "dist/cli.cjs" }, @@ -1599,6 +1859,7 @@ "integrity": "sha512-yN5oZVmRCwe5aKwzRj6736nSmKDX7pLYwsXiCj/EYmo16hODaBiT4En5btW/jhBF/seV+XMx3aYwukYC3A49DA==", "deprecated": "This version of Bootstrap is no longer supported. Please upgrade to the latest version.", "license": "MIT", + "peer": true, "engines": { "node": ">=6" } @@ -1798,8 +2059,7 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "CC-BY-4.0", - "peer": true + "license": "CC-BY-4.0" }, "node_modules/canvas-fit": { "version": "1.5.0", @@ -1896,7 +2156,6 @@ "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=6.0" } @@ -2802,8 +3061,7 @@ "version": "1.5.372", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.372.tgz", "integrity": "sha512-M3yhbAlilnwqC8D21t28UCDGHyitShTmmLRU/H+b74P6Ski16Nb9HONYEaVpMj/pwC7BEo5B95FpjODLCWbtfA==", - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/element-size": { "version": "1.1.1", @@ -2849,7 +3107,6 @@ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.0.tgz", "integrity": "sha512-SkE2t82KlkkxQRVMVLAGKxLfORGQfrkx5dkj+vlgXRVNEdPc4eZcR+J/Fvj8C+yKSFH5L0q3NFlyufOVQnCcYQ==", "license": "MIT", - "peer": true, "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" @@ -3001,8 +3258,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/es-object-atoms": { "version": "1.1.2", @@ -3204,6 +3460,7 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -3260,6 +3517,7 @@ "integrity": "sha512-tLi0JYmfiiJgtmRhoES55tENatR7y/5aXOh6cBeW+qjzl1+WwyV0arDqR65XN3/xrPZt+/1EG+xNLknV/0jWsQ==", "dev": true, "license": "ISC", + "peer": true, "peerDependencies": { "eslint": "^8.13.0", "eslint-config-standard": "^17.0.0", @@ -3288,6 +3546,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=12.0.0" }, @@ -3354,6 +3613,7 @@ "integrity": "sha512-z+vfSft/gjNQXuG9Gvyo0AYmlAXyS2e01MPKeHLTr2lSow3bOuFQDpJA3TwopgVAC2d3edMOH7aeNJIyTS4t3w==", "dev": true, "license": "MIT", + "peer": true, "peerDependencies": { "eslint": ">=2.0.0" } @@ -3410,6 +3670,7 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -3477,6 +3738,7 @@ "integrity": "sha512-jDex9s7D/Qial8AGVIHq4W7NswpUD5DPDL2RH8Lzd9EloWUuvUkHfv4FRLMipH5q2UtyurorBkPeNi1wVWNh3Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "builtins": "^5.0.1", "eslint-plugin-es": "^4.1.0", @@ -3519,12 +3781,42 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/eslint-plugin-playwright": { + "version": "2.10.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-playwright/-/eslint-plugin-playwright-2.10.4.tgz", + "integrity": "sha512-l0V/VxyqfFbtqCTxj5AdRn3Q6S/hIW4nKBnKZVleVbZ24N2My6Usj//ytX3dKKqAoSbvKck9YtSytfdZ5qjLuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "globals": "^17.3.0" + }, + "engines": { + "node": ">=16.9.0" + }, + "peerDependencies": { + "eslint": ">=8.40.0" + } + }, + "node_modules/eslint-plugin-playwright/node_modules/globals": { + "version": "17.7.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.7.0.tgz", + "integrity": "sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/eslint-plugin-promise": { "version": "6.6.0", "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.6.0.tgz", "integrity": "sha512-57Zzfw8G6+Gq7axm2Pdo3gW/Rx3h9Yywgn61uE/3elTCOePEHVrn2i5CdfBwA1BLK0Q0WqctICIUSqXZW/VprQ==", "dev": true, "license": "ISC", + "peer": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -3842,8 +4134,7 @@ "url": "https://opencollective.com/fastify" } ], - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/fastq": { "version": "1.20.1", @@ -4296,8 +4587,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "license": "BSD-2-Clause", - "peer": true + "license": "BSD-2-Clause" }, "node_modules/globals": { "version": "13.24.0", @@ -4806,7 +5096,6 @@ "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", "license": "ISC", - "peer": true, "engines": { "node": "^10 || ^12 || >= 14" }, @@ -5487,7 +5776,6 @@ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "license": "MIT", - "peer": true, "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -5510,7 +5798,8 @@ "version": "3.5.1", "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.5.1.tgz", "integrity": "sha512-XwIBPqcMn57FxfT+Go5pzySnm4KWkT1Tv7gjrpT1srtf8Weynl6R273VJ5GjkRb51IzMp5nbaPjJXMWeju2MKg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/js-stringify": { "version": "1.0.2", @@ -5700,7 +5989,6 @@ "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.2.tgz", "integrity": "sha512-DFEqQ3ihfS9blba08cLfYf1NRAIEm+dDjic073DRDc3/JspI/8wYmtDsHwd3+4hwvdxSK7PGaElfTmm0awWJ4w==", "license": "MIT", - "peer": true, "engines": { "node": ">=6.11.5" }, @@ -5907,8 +6195,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/merge2": { "version": "1.4.1", @@ -5939,7 +6226,6 @@ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -6155,8 +6441,7 @@ "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/next-tick": { "version": "1.1.0", @@ -6205,7 +6490,6 @@ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.47.tgz", "integrity": "sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==", "license": "MIT", - "peer": true, "engines": { "node": ">=18" } @@ -7245,6 +7529,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", @@ -7259,7 +7544,6 @@ "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", "license": "ISC", - "peer": true, "engines": { "node": "^10 || ^12 || >= 14" }, @@ -7272,7 +7556,6 @@ "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", "license": "MIT", - "peer": true, "dependencies": { "icss-utils": "^5.0.0", "postcss-selector-parser": "^7.0.0", @@ -7290,7 +7573,6 @@ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz", "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==", "license": "MIT", - "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -7304,7 +7586,6 @@ "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", "license": "ISC", - "peer": true, "dependencies": { "postcss-selector-parser": "^7.0.0" }, @@ -7320,7 +7601,6 @@ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz", "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==", "license": "MIT", - "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -7334,7 +7614,6 @@ "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", "license": "ISC", - "peer": true, "dependencies": { "icss-utils": "^5.0.0" }, @@ -7362,8 +7641,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/potpack": { "version": "1.0.2", @@ -7570,6 +7848,7 @@ "integrity": "sha512-CcFFU9+cXu/0xXXQbE1Zus0u9l3OCXqU+2sw4NcnFGEml8RoCUqrSdVNoVkg2SYcNvcfcnV6h+ctmE5+Ptgj1w==", "dev": true, "license": "ISC", + "peer": true, "dependencies": { "acorn": "^4.0.1", "commander": "^2.9.0", @@ -8316,7 +8595,6 @@ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -8596,7 +8874,6 @@ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", "license": "MIT", - "peer": true, "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", @@ -8633,7 +8910,6 @@ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3" }, @@ -8645,8 +8921,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/select2": { "version": "4.1.0-rc.0", @@ -8899,7 +9174,6 @@ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "license": "MIT", - "peer": true, "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -9645,7 +9919,6 @@ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", "license": "MIT", - "peer": true, "engines": { "node": ">=6" }, @@ -9659,7 +9932,6 @@ "resolved": "https://registry.npmjs.org/terser/-/terser-5.48.0.tgz", "integrity": "sha512-J/9An6vs9Us6wKRriSFXBWdRZapREHqFzdNUKk0pmu804EMR6dr6winwo7e5JDxN4xahxQsuysyYFwlwj4XN/Q==", "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", @@ -9678,7 +9950,6 @@ "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.6.1.tgz", "integrity": "sha512-201R5j+sJpK8nFWwKVyNfZot8FaJbLZDq5evriVzbV1wDtSXDjRUDRfJzHpAaxFDMEhsZL1QkeqM61wgsS3KaQ==", "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", @@ -9767,6 +10038,55 @@ "node": "*" } }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/tinyqueue": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-2.0.3.tgz", @@ -9842,6 +10162,19 @@ "tree-kill": "cli.js" } }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, "node_modules/tsconfig-paths": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", @@ -10002,6 +10335,21 @@ "integrity": "sha512-lVGVHvYl7UJDFB9vN8r7NHw3sVm7Rjeow6b9AeABc/J+2mDaCkmcdVtw3QZnsJW39P+xm5zeggIj9gLHYGn9Iw==", "license": "MIT" }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "devOptional": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/unbox-primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", @@ -10031,8 +10379,7 @@ "version": "7.24.6", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/unique-filename": { "version": "1.1.1", @@ -10087,7 +10434,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" @@ -10203,6 +10549,7 @@ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", @@ -10458,7 +10805,6 @@ "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.2.tgz", "integrity": "sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg==", "license": "MIT", - "peer": true, "dependencies": { "graceful-fs": "^4.1.2" }, @@ -10543,7 +10889,6 @@ "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.5.0.tgz", "integrity": "sha512-HPuy+uuoTCaaoEoI1LQ3JN9+vrPBvEesnnX1jADHy728cHSMlq4wUc4afYqahq2B1mhQVZxCXOkNTnXltr+2vQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=10.13.0" } @@ -10553,7 +10898,6 @@ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "license": "BSD-2-Clause", - "peer": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -10567,7 +10911,6 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "license": "BSD-2-Clause", - "peer": true, "engines": { "node": ">=4.0" } diff --git a/histomicsui/web_client/package.json b/histomicsui/web_client/package.json index d668be33..f3fcedc8 100644 --- a/histomicsui/web_client/package.json +++ b/histomicsui/web_client/package.json @@ -21,8 +21,8 @@ "watch": "concurrently npm:watch-app npm:watch-plugin", "watch-app": "BUILD_TARGET=app vite build --watch", "watch-plugin": "BUILD_TARGET=plugin vite build --watch", - "lint": "eslint --cache . && eslint --no-eslintrc -c ./package.json --cache ../../tests && pug-lint . && stylus-supremacy format --compare ./**/*.styl --options package.json", - "format": "eslint --cache --fix . && eslint --no-eslintrc -c ./package.json --cache --fix ../../tests && stylus-supremacy format ./**/*.styl --replace --options package.json", + "lint": "eslint --cache . && eslint --cache --ext .ts ./tests && eslint --no-eslintrc -c ./package.json --cache ../../tests && pug-lint . && stylus-supremacy format --compare ./**/*.styl --options package.json", + "format": "eslint --cache --fix . && eslint --cache --fix --ext .ts ./tests && eslint --no-eslintrc -c ./package.json --cache --fix ../../tests && stylus-supremacy format ./**/*.styl --replace --options package.json", "test": "playwright test" }, "dependencies": { @@ -47,6 +47,8 @@ "@girder/eslint-config": "*", "@girder/pug-lint-config": "*", "@playwright/test": "^1.47.2", + "@typescript-eslint/eslint-plugin": "^8.62.1", + "@typescript-eslint/parser": "^8.62.1", "@vitejs/plugin-vue2": "^2.3.1", "concurrently": "^8.2.2", "eslint": "^8.20.0", @@ -55,6 +57,7 @@ "eslint-plugin-backbone": "^2.1.1", "eslint-plugin-import": "^2.25.3", "eslint-plugin-n": "^15.2.4", + "eslint-plugin-playwright": "^2.10.4", "eslint-plugin-promise": "^6.0.0", "eslint-plugin-vue": "~9.14.0", "npm-run-all": "^4.1.5", @@ -70,12 +73,49 @@ "@girder", "plugin:vue/recommended" ], + "env": { + "jquery": true + }, "overrides": [ { "files": [ "*.js", "*.vue" ] + }, + { + "files": [ + "**/*.ts" + ], + "parser": "@typescript-eslint/parser", + "plugins": [ + "@typescript-eslint" + ], + "parserOptions": { + "ecmaVersion": 2022, + "sourceType": "module" + }, + "rules": { + "no-unused-vars": "off", + "promise/no-native": "off" + } + }, + { + "files": [ + "tests/**/*.ts" + ], + "parser": "@typescript-eslint/parser", + "plugins": [ + "@typescript-eslint", + "playwright" + ], + "extends": [ + "plugin:playwright/recommended" + ], + "parserOptions": { + "ecmaVersion": 2022, + "sourceType": "module" + } } ], "rules": { diff --git a/histomicsui/web_client/playwright.config.ts b/histomicsui/web_client/playwright.config.ts index bd7f3ac4..6f0aaa60 100644 --- a/histomicsui/web_client/playwright.config.ts +++ b/histomicsui/web_client/playwright.config.ts @@ -1,30 +1,30 @@ -import { defineConfig, devices } from '@playwright/test'; +import {defineConfig, devices} from '@playwright/test'; export default defineConfig({ - testDir: './tests', - fullyParallel: false, - forbidOnly: !!process.env.CI, - retries: process.env.CI ? 2 : 0, - workers: process.env.CI ? 1 : 2, - reporter: [['html', { host: '0.0.0.0'}]], + testDir: './tests', + fullyParallel: false, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: process.env.CI ? 1 : 2, + reporter: [['html', {host: '0.0.0.0'}]], - use: { - actionTimeout: 30000, - trace: 'on-first-retry', - headless: !!process.env.HEADLESS, - screenshot: 'only-on-failure', - }, - - projects: [ - { - name: 'chromium', - use: { ...devices['Desktop Chrome'] }, + use: { + actionTimeout: 30000, + trace: 'on-first-retry', + headless: !!process.env.HEADLESS, + screenshot: 'only-on-failure' }, - ], - webServer: { - command: 'npx vite dev --port 5173', - port: 5173, - reuseExistingServer: false - }, + projects: [ + { + name: 'chromium', + use: {...devices['Desktop Chrome']} + } + ], + + webServer: { + command: 'npx vite dev --port 5173', + port: 5173, + reuseExistingServer: false + } }); diff --git a/histomicsui/web_client/tests/server.ts b/histomicsui/web_client/tests/server.ts index 6602096c..75e38fc3 100644 --- a/histomicsui/web_client/tests/server.ts +++ b/histomicsui/web_client/tests/server.ts @@ -1,112 +1,113 @@ -import { spawn, ChildProcessWithoutNullStreams } from 'child_process'; -import { expect, test } from '@playwright/test'; +import {spawn, ChildProcessWithoutNullStreams} from 'child_process'; -const mongoUri = process.env.GIRDER_CLIENT_TESTING_MONGO_URI ?? 'mongodb://mongodb:27017'; +import {expect, test} from '@playwright/test'; + +const mongoUri = process.env.GIRDER_CLIENT_TESTING_MONGO_URI ? 'mongodb://mongodb:27017' : 'mongodb://localhost:27017'; const startServer = async (port: number) => { - const database = `${mongoUri}/girder-${port}`; - let command, args; + const database = `${mongoUri}/girder-${port}`; + let command, args; - if (process.env.HISTOMICS_PLAYWRIGHT_DEV) { + if (process.env.HISTOMICS_PLAYWRIGHT_DEV) { // For recording with playwright, it's run through VSCode and Girder is run via docker-compose // If running through docker-compose, wrap the command with `nohup` to ensure the spawned // process is kept alive on linux, where child processes will, by default, receive a SIGHUP // when the parent process is killed. - command = 'nohup'; - args = [ - 'docker', - 'compose', - 'run', - '-p', `${port}:${port}`, - '--rm', - 'histomicsui', - 'girder', - 'serve', - '--host', '0.0.0.0', - '--with-temp-assetstore', - '--port', `${port}`, - '--database', database, - ]; - } else { + command = 'nohup'; + args = [ + 'docker', + 'compose', + 'run', + '-p', `${port}:${port}`, + '--rm', + 'histomicsui', + 'girder', + 'serve', + '--host', '0.0.0.0', + '--with-temp-assetstore', + '--port', `${port}`, + '--database', database + ]; + } else { // For running tests in CI, this process is run inside of the histomicsui docker container - command = 'girder'; - args = [ - 'serve', - '--database', database, - '--port', `${port}`, - '--with-temp-assetstore', - ]; - } + command = 'girder'; + args = [ + 'serve', + '--database', database, + '--port', `${port}`, + '--with-temp-assetstore' + ]; + } - const serverProcess = spawn(command, args, { - env: { - ...process.env, - GIRDER_SETTING_CORE_CORS_ALLOW_ORIGIN: '*', - GIRDER_EMAIL_TO_CONSOLE: 'true', - }, - // These two options are necessary to make Girder server subprocess survive end of - // the test process. - detached: true, + const serverProcess = spawn(command, args, { + env: { + ...process.env, + GIRDER_SETTING_CORE_CORS_ALLOW_ORIGIN: '*', + GIRDER_EMAIL_TO_CONSOLE: 'true' + }, + // These two options are necessary to make Girder server subprocess survive end of + // the test process. + detached: true // stdio: 'ignore', - }); - await new Promise((resolve) => { - serverProcess?.stdout.on('data', (data: string) => { - console.log(`stdout: ${data}`); - if (data.includes('Girder server running')) { - resolve(); - } - }); - serverProcess?.stderr.on('data', (data: string) => { - console.error(`stderr: ${data}`); }); - serverProcess?.on('close', (code) => { - console.log(`child process exited with code ${code}`); + await new Promise((resolve) => { + serverProcess?.stdout.on('data', (data: string) => { + console.log(`stdout: ${data}`); + if (data.includes('Girder server running')) { + resolve(); + } + }); + serverProcess?.stderr.on('data', (data: string) => { + console.error(`stderr: ${data}`); + }); + serverProcess?.on('close', (code) => { + console.log(`child process exited with code ${code}`); + }); }); - }); - return serverProcess; + return serverProcess; }; export const setupServer = () => { - let serverProcess: ChildProcessWithoutNullStreams; - const port = Math.floor(Math.random() * 2000 + 6000); + let serverProcess: ChildProcessWithoutNullStreams; + const port = Math.floor(Math.random() * 2000 + 6000); - test.beforeAll(async () => { - serverProcess = await startServer(port); - }); + test.beforeAll(async () => { + serverProcess = await startServer(port); + }); - test.afterAll(async () => { - if (process.env.GIRDER_CLIENT_TESTING_KEEP_SERVER_ALIVE) { - if (serverProcess) { - console.log('WARNING: Girder server is being kept alive after test ends. Use the following to kill it:'); - console.log(`kill ${serverProcess?.pid}`); - } - return; - } + test.beforeEach(async ({page}) => { + await page.goto(`http://0.0.0.0:${port}/`); + await expect(page.getByRole('link', {name: 'About'})).toBeVisible(); + }); - serverProcess?.kill(); + test.afterAll(async () => { + if (process.env.GIRDER_CLIENT_TESTING_KEEP_SERVER_ALIVE) { + if (serverProcess) { + console.log('WARNING: Girder server is being kept alive after test ends. Use the following to kill it:'); + console.log(`kill ${serverProcess?.pid}`); + } + return; + } - const mongoshProcess = spawn('mongosh', [`${mongoUri}/girder-${port}`, '--eval', 'db.dropDatabase();']); + serverProcess?.kill(); - await new Promise((resolve) => { - mongoshProcess?.on('close', (code) => { - if (code === 0) { - console.log('mongo database cleaned up'); - } else { - console.error('mongo database cleanup failed with code', code); - } + const mongoshProcess = spawn('mongosh', [`${mongoUri}/girder-${port}`, '--eval', 'db.dropDatabase();']); - resolve(); - }); + await new Promise((resolve) => { + mongoshProcess?.on('close', (code) => { + if (code === 0) { + console.log('mongo database cleaned up'); + } else { + console.error('mongo database cleanup failed with code', code); + } - mongoshProcess?.on('error', (err) => { - console.error('mongosh process error -- database not cleaned up', err); - resolve(); - }); - }); - }); + resolve(); + }); - test.beforeEach(async ({ page }) => { - await page.goto(`http://0.0.0.0:${port}/`); - await expect(page.getByRole('link', { name: 'About' })).toBeVisible(); - }); + mongoshProcess?.on('error', (err) => { + console.error('mongosh process error -- database not cleaned up', err); + resolve(); + }); + }); + }); }; diff --git a/histomicsui/web_client/tests/spec/viewer.spec.ts b/histomicsui/web_client/tests/spec/viewer.spec.ts index 800580a5..9654d560 100644 --- a/histomicsui/web_client/tests/spec/viewer.spec.ts +++ b/histomicsui/web_client/tests/spec/viewer.spec.ts @@ -1,19 +1,19 @@ -import { test } from '@playwright/test'; +import {test} from '@playwright/test'; -import { createUser, uploadSampleFile } from '../util'; -import { setupServer } from '../server'; +import {createUser, uploadSampleFile} from '../util'; +import {setupServer} from '../server'; test.describe('Test image viewer', () => { - setupServer(); + setupServer(); - test('Upload SVS large image and ensure thumbnail renders', async ({ page }) => { - await createUser(page); - await page.locator('#g-app-header-container').getByText('firstlast').click(); - await page.getByRole('link', { name: ' My folders' }).click(); - await page.getByRole('link', { name: ' Private ' }).click(); - await uploadSampleFile(page, 'sample_svs_image.TCGA-DU-6399-01A-01-TS1.e8eb65de-d63e-42db-af6f-14fefbbdf7bd.svs'); + test('Upload SVS large image and ensure thumbnail renders', async ({page}) => { + await createUser(page); + await page.locator('#g-app-header-container').getByText('firstlast').click(); + await page.getByRole('link', {name: ' My folders'}).click(); + await page.getByRole('link', {name: ' Private '}).click(); + await uploadSampleFile(page, 'sample_svs_image.TCGA-DU-6399-01A-01-TS1.e8eb65de-d63e-42db-af6f-14fefbbdf7bd.svs'); - const locator = page.locator('.large_image_thumbnail>img.loaded'); - await locator.waitFor({ state: 'visible' }); - }); + const locator = page.locator('.large_image_thumbnail>img.loaded'); + await locator.waitFor({state: 'visible'}); + }); }); diff --git a/histomicsui/web_client/tests/util.ts b/histomicsui/web_client/tests/util.ts index 55f90219..15b8735f 100644 --- a/histomicsui/web_client/tests/util.ts +++ b/histomicsui/web_client/tests/util.ts @@ -1,15 +1,16 @@ import path from 'path'; -import { fileURLToPath } from 'url'; -import { expect, type Page } from '@playwright/test'; +import {fileURLToPath} from 'url'; + +import {expect, type Page} from '@playwright/test'; export const waitForDialog = async (page: Page) => { - await expect(page.locator('#g-dialog-container')).toBeVisible(); - await expect(page.locator('.modal-backdrop')).toBeVisible(); - await delay(500); // TODO this is bad - // @ts-ignore - // while (await page.evaluate(() => window.girder._inTransition)) { - // await delay(100); - // } + await expect(page.locator('#g-dialog-container')).toBeVisible(); + await expect(page.locator('.modal-backdrop')).toBeVisible(); + await delay(500); // TODO this is bad + // @ts-ignore + // while (await page.evaluate(() => window.girder._inTransition)) { + // await delay(100); + // } // waitsFor(function () { // return !girder._inTransition; // }, 'dialog transitions to finish'); @@ -19,73 +20,73 @@ export const waitForDialog = async (page: Page) => { }; export const logout = async (page: Page) => { - await page.locator('.g-user-dropdown-link').click(); - await expect(page.locator('.g-logout')).toBeVisible(); - await page.locator('.g-logout').click(); - await expect(page.locator('.g-register')).toBeVisible(); - await expect(page.locator('.g-login')).toBeVisible(); - await expect(page.locator('.g-user-dropdown-link')).toBeHidden(); + await page.locator('.g-user-dropdown-link').click(); + await expect(page.locator('.g-logout')).toBeVisible(); + await page.locator('.g-logout').click(); + await expect(page.locator('.g-register')).toBeVisible(); + await expect(page.locator('.g-login')).toBeVisible(); + await expect(page.locator('.g-user-dropdown-link')).toBeHidden(); }; export const createUser = async ( - page: Page, - login: string = 'firstlast', - email: string = 'email@email.com', - firstName: string = 'first', - lastName: string = 'last', - password: string = 'password', + page: Page, + login: string = 'firstlast', + email: string = 'email@email.com', + firstName: string = 'first', + lastName: string = 'last', + password: string = 'password' ) => { - await expect(page.locator('.g-register')).toBeVisible(); - await page.locator('.g-register').click(); - await waitForDialog(page); - await expect(page.locator('input#g-email')).toBeVisible(); - await page.locator('#g-login').fill(login, { timeout: 1000 }); - await page.locator('#g-email').fill(email, { timeout: 1000 }); - await page.locator('#g-firstName').fill(firstName, { timeout: 1000 }); - await page.locator('#g-lastName').fill(lastName, { timeout: 1000 }); - await page.locator('#g-password').fill(password, { timeout: 1000 }); - await page.locator('#g-password2').fill(password, { timeout: 1000 }); - await page.locator('#g-register-button').click(); - await expect(page.locator('.g-register')).toBeHidden(); - await expect(page.locator('.g-login')).toBeHidden(); - await expect(page.locator('.g-user-dropdown-link')).toBeVisible(); - await expect(page.locator('.g-user-dropdown-link')).toContainText(login); + await expect(page.locator('.g-register')).toBeVisible(); + await page.locator('.g-register').click(); + await waitForDialog(page); + await expect(page.locator('input#g-email')).toBeVisible(); + await page.locator('#g-login').fill(login, {timeout: 1000}); + await page.locator('#g-email').fill(email, {timeout: 1000}); + await page.locator('#g-firstName').fill(firstName, {timeout: 1000}); + await page.locator('#g-lastName').fill(lastName, {timeout: 1000}); + await page.locator('#g-password').fill(password, {timeout: 1000}); + await page.locator('#g-password2').fill(password, {timeout: 1000}); + await page.locator('#g-register-button').click(); + await expect(page.locator('.g-register')).toBeHidden(); + await expect(page.locator('.g-login')).toBeHidden(); + await expect(page.locator('.g-user-dropdown-link')).toBeVisible(); + await expect(page.locator('.g-user-dropdown-link')).toContainText(login); }; export const login = async ( - page: Page, - login: string, - password: string = 'password', + page: Page, + login: string, + password: string = 'password' ) => { - await expect(page.locator('.g-login')).toBeVisible(); - await page.locator('.g-login').click(); - await waitForDialog(page); - await expect(page.locator('#g-login')).toBeVisible(); - await page.locator('#g-login').fill(login, { timeout: 1000 }); - await page.locator('#g-password').fill(password, { timeout: 1000 }); - await page.locator('#g-login-button').click(); - await expect(page.locator('.g-register')).toBeHidden(); - await expect(page.locator('.g-login')).toBeHidden(); - await expect(page.locator('.g-user-dropdown-link')).toBeVisible(); - await expect(page.locator('.g-user-dropdown-link')).toContainText(login); + await expect(page.locator('.g-login')).toBeVisible(); + await page.locator('.g-login').click(); + await waitForDialog(page); + await expect(page.locator('#g-login')).toBeVisible(); + await page.locator('#g-login').fill(login, {timeout: 1000}); + await page.locator('#g-password').fill(password, {timeout: 1000}); + await page.locator('#g-login-button').click(); + await expect(page.locator('.g-register')).toBeHidden(); + await expect(page.locator('.g-login')).toBeHidden(); + await expect(page.locator('.g-user-dropdown-link')).toBeVisible(); + await expect(page.locator('.g-user-dropdown-link')).toContainText(login); }; export const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); export const upload = async (page: Page, file: string | string[], awaitSuccess: boolean = true) => { - // This should be called from a folder view, where the upload button is visible. - // At the end, the file will be uploaded and you'll be back on the folder view. - await page.locator('.g-upload-here-button').first().click(); - await expect(page.locator('.g-drop-zone')).toBeVisible(); - await page.locator('#g-files').setInputFiles(file); - await page.locator('.g-start-upload').click(); - if (awaitSuccess) { - await expect(page.locator('.g-start-upload')).toBeHidden(); - } + // This should be called from a folder view, where the upload button is visible. + // At the end, the file will be uploaded and you'll be back on the folder view. + await page.locator('.g-upload-here-button').first().click(); + await expect(page.locator('.g-drop-zone')).toBeVisible(); + await page.locator('#g-files').setInputFiles(file); + await page.locator('.g-start-upload').click(); + if (awaitSuccess) { + await expect(page.locator('.g-start-upload')).toBeHidden(); + } }; export const uploadSampleFile = async (page: Page, filename: string) => { - const __filename = fileURLToPath(import.meta.url); - const sampleFile = path.join(path.dirname(__filename), '..', '..', '..', 'sample_data', filename); - await upload(page, sampleFile); + const __filename = fileURLToPath(import.meta.url); + const sampleFile = path.join(path.dirname(__filename), '..', '..', '..', 'sample_data', filename); + await upload(page, sampleFile); }; diff --git a/histomicsui/web_client/vite-env.d.ts b/histomicsui/web_client/vite-env.d.ts index 40c880e7..dad8ee14 100644 --- a/histomicsui/web_client/vite-env.d.ts +++ b/histomicsui/web_client/vite-env.d.ts @@ -1,5 +1,5 @@ /// -import { type Girder } from '@girder/core'; +import {type Girder} from '@girder/core'; declare global { const girder: Girder; diff --git a/histomicsui/web_client/vite.config.ts b/histomicsui/web_client/vite.config.ts index b3a6a925..01e7aa7b 100644 --- a/histomicsui/web_client/vite.config.ts +++ b/histomicsui/web_client/vite.config.ts @@ -1,85 +1,85 @@ import fs from 'fs'; -import path, { resolve } from 'path'; +import path, {resolve} from 'path'; -import { defineConfig, type UserConfig } from 'vite'; -import { viteStaticCopy } from 'vite-plugin-static-copy' -import { compileClient } from 'pug'; +import {defineConfig, type UserConfig} from 'vite'; +import {viteStaticCopy} from 'vite-plugin-static-copy'; +import {compileClient} from 'pug'; import vue from '@vitejs/plugin-vue2'; function pugPlugin() { - return { - name: 'pug', - transform(src: string, id: string) { - if (id.endsWith('.pug')) { - return { - code: `${compileClient(src, {filename: id})}\nexport default template`, - map: null, - }; - } - }, - }; + return { + name: 'pug', + transform(src: string, id: string) { + if (id.endsWith('.pug')) { + return { + code: `${compileClient(src, {filename: id})}\nexport default template`, + map: null + }; + } + } + }; } function inlineFaviconPlugin(relFaviconPath, mimeType) { - return { - name: 'inline-favicon', - transformIndexHtml(html, ctx) { - if (ctx && ctx.server) { - return html; - } + return { + name: 'inline-favicon', + transformIndexHtml(html, ctx) { + if (ctx && ctx.server) { + return html; + } - const faviconAbsPath = path.resolve(process.cwd(), relFaviconPath); - try { - const faviconData = fs.readFileSync(faviconAbsPath); - const base64 = faviconData.toString('base64'); - const dataUri = `data:${mimeType};base64,${base64}`; - return html.replace( - /]*href=["'][^"']*["'][^>]*>/i, - `` - ); - } catch (err) { - console.warn(`[inline-favicon] Failed to inline favicon ${relFaviconPath}:`, err); - return html; - } - } - }; + const faviconAbsPath = path.resolve(process.cwd(), relFaviconPath); + try { + const faviconData = fs.readFileSync(faviconAbsPath); + const base64 = faviconData.toString('base64'); + const dataUri = `data:${mimeType};base64,${base64}`; + return html.replace( + /]*href=["'][^"']*["'][^>]*>/i, + `` + ); + } catch (err) { + console.warn(`[inline-favicon] Failed to inline favicon ${relFaviconPath}:`, err); + return html; + } + } + }; } const buildTarget = process.env.BUILD_TARGET; const config: UserConfig = { - plugins: [ - pugPlugin(), - vue(), - inlineFaviconPlugin('static/favicon.png', 'image/png'), - viteStaticCopy({ - targets: [ - { - src: 'node_modules/plotly.js/dist/plotly.min.js', - dest: 'extra', - rename: () => 'plotly.js', - } - ] - }) - ], - build: { - sourcemap: !process.env.SKIP_SOURCE_MAPS - }, - define: { - __BUILD_TIMESTAMP__: `${+new Date()}`, - 'process.env': {}, // for legacy Vue 2 - }, + plugins: [ + pugPlugin(), + vue(), + inlineFaviconPlugin('static/favicon.png', 'image/png'), + viteStaticCopy({ + targets: [ + { + src: 'node_modules/plotly.js/dist/plotly.min.js', + dest: 'extra', + rename: () => 'plotly.js' + } + ] + }) + ], + build: { + sourcemap: !process.env.SKIP_SOURCE_MAPS + }, + define: { + __BUILD_TIMESTAMP__: `${+new Date()}`, + 'process.env': {} // for legacy Vue 2 + } }; if (buildTarget === 'plugin') { - config.build.lib = { - entry: resolve(__dirname, 'main.js'), - name: 'GirderPluginHistomicsUI', - fileName: 'girder-plugin-histomics-ui', - }; + config.build.lib = { + entry: resolve(__dirname, 'main.js'), + name: 'GirderPluginHistomicsUI', + fileName: 'girder-plugin-histomics-ui' + }; } else { - config.build.outDir = 'dist-app'; - config.base = './'; + config.build.outDir = 'dist-app'; + config.base = './'; } export default defineConfig(config);