From ed8f73a032407229cec6db55bf05019ad48f212c Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Tue, 1 Sep 2026 11:44:44 +0200 Subject: [PATCH] chore(lint): let the linter see tests/ and scripts/ `npm run lint` was `eslint src`, so two whole trees were never linted. Across the fleet that hid roughly 3,900 errors, none of which any CI leg had shown. Most of it was the config, not the code, and the same two defects were in every app. **scripts/ had no config block at all.** These are CommonJS Node CLI checkers, and flat config defaults every `.js` to ESM with browser-ish globals, so eslint read the CommonJS wrapper itself as undefined identifiers: `require`, `process`, `__dirname`, `__filename`, `module`. A `scripts/**` block now declares the environment. Declaring beats suppressing here: `no-undef` is the rule that catches a genuinely misspelled identifier, and dozens of fake findings would bury a real one. A second block covers `scripts/**/*.mjs`, which is ESM and needs Node's globals without the CommonJS wrapper. **The tests block applied a non-TypeScript-aware rule to TypeScript.** It named `tests/**/*.ts` while setting the CORE `no-unused-vars`, which v9 deliberately turns off for `.ts` in favour of the `@typescript-eslint` version. The core rule reads the parameter names inside a function TYPE as bindings, so t?: (app: string, key: string) => string reports `app` and `key` as unused variables, and every unused `catch (e)` in a `.ts` spec reports twice. The block is split now: `.js`/`.mjs` on the core rule, `.ts`/`.tsx` on the TypeScript one, same patterns on both. Also: stale `eslint-disable` comments naming plugins eslint 10 no longer registers, which are themselves errors ("Definition for rule ... was not found"), and a rule that must not parse shell scripts. The genuinely real findings were the useful part: dead locals, unused imports, dead helper functions, unused `catch` bindings, extensionless relative imports, and a handful of `== null` comparisons spelled out so they still match null AND undefined. Verified per app: `npm run lint` 0 errors over src + tests + scripts, `prettier --check` clean, and the unit suite still green. --- eslint.config.mjs | 154 +++++++++++++++++- package.json | 2 +- scripts/build-l10n-js.js | 3 + scripts/check-integration-parity.js | 36 ++-- scripts/check-l10n.js | 17 +- scripts/check-schema-l10n.js | 3 + scripts/clean-l10n.js | 10 +- scripts/find-unwrapped.js | 23 ++- scripts/l10n-ai.js | 58 +++++-- scripts/lib/l10n.js | 3 + .../configuration-export-import.spec.ts | 4 +- .../consumer-rate-limiting.api.spec.ts | 2 +- .../api-direct/endpoint-runtime.api.spec.ts | 2 +- .../e2e/api-direct/rule-pipeline.api.spec.ts | 2 +- .../synchronization-engine.api.spec.ts | 2 +- tests/e2e/api-direct/user-management.spec.ts | 4 +- tests/e2e/docs-screenshots.spec.ts | 6 +- tests/e2e/global-setup.ts | 15 +- tests/e2e/playwright.config.ts | 3 +- .../e2e/regression/dead-letter-replay.spec.ts | 6 +- .../regression/dead-letters-merged.spec.ts | 4 +- tests/e2e/regression/journeys.spec.ts | 18 +- tests/e2e/regression/manifest-pages.spec.ts | 18 +- .../regression/migration-round-trip.spec.ts | 13 +- .../regression/spa-root-resolution.spec.ts | 8 +- tests/e2e/regression/synced-from-leaf.spec.ts | 12 +- tests/e2e/regression/webhook-signing.spec.ts | 6 +- tests/e2e/spec-coverage/_helpers.ts | 6 +- .../action-authorization.spec.ts | 2 +- .../spec-coverage/api-product-gateway.spec.ts | 4 +- .../cloud-event-management.spec.ts | 6 +- .../configuration-export-import.spec.ts | 4 +- .../configuration-import-export-ui.spec.ts | 4 +- .../spec-coverage/connector-catalog.spec.ts | 2 +- .../spec-coverage/consumer-management.spec.ts | 6 +- tests/e2e/spec-coverage/dashboard.spec.ts | 8 +- .../e2e/spec-coverage/dead-letters-ui.spec.ts | 22 +-- .../demo-data-setup-step.spec.ts | 8 +- .../spec-coverage/endpoint-runtime.spec.ts | 6 +- .../spec-coverage/features-roadmap.spec.ts | 4 +- .../spec-coverage/flow-native-sync.spec.ts | 11 +- .../spec-coverage/flow-orchestration.spec.ts | 8 +- .../e2e/spec-coverage/job-management.spec.ts | 6 +- .../spec-coverage/mapping-and-search.spec.ts | 4 +- .../spec-coverage/nav-and-index-pages.spec.ts | 8 +- .../spec-coverage/prometheus-metrics.spec.ts | 4 +- tests/e2e/spec-coverage/rule-pipeline.spec.ts | 6 +- .../spec-coverage/source-management.spec.ts | 6 +- .../sync-editor-bridge-types.spec.ts | 14 +- .../synchronization-engine.spec.ts | 6 +- .../e2e/spec-coverage/user-management.spec.ts | 4 +- tests/e2e/spec-coverage/webhooks.spec.ts | 4 +- tests/e2e/support/appRoot.ts | 4 +- tests/e2e/visual/_visual-helpers.ts | 6 +- tests/e2e/visual/integriq.visual.spec.ts | 2 +- tests/e2e/workflows/_fixture.ts | 28 ++-- .../e2e/workflows/source-mapping-crud.spec.ts | 17 +- .../synchronization-workflow.spec.ts | 17 +- tests/l10n/check-l10n-parity.js | 6 +- tests/l10n/check-l10n.js | 7 +- tests/validate-json-strict.js | 5 +- tests/validate-manifest.js | 4 +- tests/vitest/actionFormsShared.spec.js | 4 +- .../buildAuthenticationConfiguration.spec.js | 8 +- tests/vitest/catalogStore.spec.js | 2 +- tests/vitest/consumerDraft.spec.js | 6 +- tests/vitest/editorModalSlotContract.spec.js | 3 +- tests/vitest/formsBridge.spec.js | 12 +- tests/vitest/jobDraft.spec.js | 4 +- .../liveObjectSubscriptionCleanup.spec.js | 4 +- tests/vitest/ncButtonSubmitType.spec.js | 4 +- tests/vitest/routerRef.spec.js | 4 +- tests/vitest/ruleDraft.spec.js | 6 +- tests/vitest/runTargets.spec.js | 4 +- tests/vitest/sourceCredentialRef.spec.js | 12 +- tests/vitest/tablesBridge.spec.js | 10 +- 76 files changed, 495 insertions(+), 281 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index a4bbb8c76..d6b672728 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -144,11 +144,44 @@ export default [ 'no-console': 'off', 'n/no-process-exit': 'off', 'n/hashbang': 'off', - // `_` / `__` as a deliberate throwaway binding β€” `catch (_)`, a - // discarded destructuring slot. Narrow on purpose: the pattern matches - // UNDERSCORES ONLY, so a real name that happens to start with `_` is - // still reported. v9 drives plain `.js` through the CORE rule (the - // `@typescript-eslint` swap is per-file-type), so it is set here. + // Tests import devDependencies by definition; this rule is about what + // ships in the published package, which tests/ never does. + 'n/no-unpublished-import': 'off', + // πŸ”΄ COMMENTS ONLY, and the exception is load-bearing. Four + // api-direct specs document the `testIgnore` glob that excludes them + // from the gate-19 UI run, and a block comment cannot contain the + // literal `**` + `/` because that closes it at `*/`. The files carry a + // ZERO-WIDTH SPACE (U+200B) between the two to break the sequence. + // + // Deleting the character to satisfy the rule would terminate the + // comment early and break the file. In CODE an invisible character is + // a genuine hazard β€” a look-alike inside an identifier or a string β€” + // and the rule still catches that. In a comment it cannot change + // behaviour. + 'no-irregular-whitespace': ['error', { skipComments: true }], + }, + }, + + { + // `_` / `__` as a deliberate throwaway binding β€” `catch (_)`, a discarded + // destructuring slot. Narrow on purpose: the pattern matches UNDERSCORES + // ONLY, so a real name that happens to start with `_` is still reported. + // + // πŸ”΄ `.js` / `.mjs` ONLY, NOT `.ts`. The CORE rule is not TypeScript-aware: + // applied to a `.ts` file it reads the parameter NAMES inside a function + // TYPE as bindings and reports them unused. Measured on humaniq β€” + // + // t?: (app: string, key: string) => string + // + // produced four `no-unused-vars` errors for `app` and `key`, which are + // documentation, not variables. The same mis-scoping made every unused + // `catch (e)` in a `.ts` spec report TWICE, once per rule. + // + // v9 already turns the core rule off for `.ts` and drives + // `@typescript-eslint/no-unused-vars` instead; naming `.ts` here switched + // it back on. TypeScript files are handled by the block below. + files: ['tests/**/*.js', 'tests/**/*.mjs'], + rules: { 'no-unused-vars': [ 'error', { @@ -165,12 +198,117 @@ export default [ ignoreRestSiblings: true, }, ], - // Tests import devDependencies by definition; this rule is about what - // ships in the published package, which tests/ never does. - 'n/no-unpublished-import': 'off', }, }, + { + // The TypeScript half of the block above. Same intent, same patterns, on + // the rule that actually understands the language: it knows a name inside + // a function type is not a binding, so type annotations stay quiet while a + // genuinely dead local is still reported. + files: ['tests/**/*.ts', 'tests/**/*.tsx'], + rules: { + '@typescript-eslint/no-unused-vars': [ + 'error', + { + varsIgnorePattern: '^_+$', + caughtErrors: 'all', + caughtErrorsIgnorePattern: '^_+$', + argsIgnorePattern: '^_', + ignoreRestSiblings: true, + }, + ], + }, + }, + + { + // πŸ”΄ Node-side CLI tooling under `scripts/`, which is COMMONJS. Flat + // config defaults every `.js` to ESM with browser-ish globals, so without + // this block eslint reports the CommonJS wrapper itself as undefined + // identifiers. Measured on this app: 52 of the 233 errors under + // `tests/` + `scripts/` were `no-undef`, ALL of them in `scripts/`, and + // all five names were the environment rather than a typo β€” `process` 23, + // `require` 20, `__dirname` 6, `__filename` 2, `module` 1. + // + // This is describing the environment, not relaxing a rule, and it is the + // same argument the test-globals block below makes: declaring them keeps + // `no-undef` able to do its real job, which is catching a genuinely + // misspelled identifier. Suppressing the rule instead would bury that. + // + // `no-console` is off because printing its report is what a CLI checker + // is FOR. + // + // πŸ”΄ NO `n/*` ENTRIES HERE, DELIBERATELY. `eslint-plugin-n` is NOT + // registered for these files under eslint 10 + @nextcloud/eslint-config + // 9, so `'n/no-process-exit': 'off'` would be dead config that reads as + // if it were doing something. Measured both ways on this app: 0 `n/` + // findings with the entries and 0 without. + // + // What DID report was the opposite β€” four `scripts/*.js` carried + // `/* eslint-disable n/no-process-exit */` and `/* eslint-disable + // n/shebang */` left over from the eslintrc era, and an inline disable + // naming an unregistered plugin is itself an error ("Definition for rule + // 'n/shebang' was not found"). Those 8 comments are removed; do not add + // `n/*` rules back to replace them. + // + // ⚠️ `.js` and `.cjs` ONLY. A `scripts/*.mjs` is genuinely ESM and must + // keep the default `sourceType`, or `import` stops parsing there. + files: ['scripts/**/*.js', 'scripts/**/*.cjs'], + languageOptions: { + sourceType: 'commonjs', + globals: { + require: 'readonly', + module: 'writable', + exports: 'writable', + process: 'readonly', + __dirname: 'readonly', + __filename: 'readonly', + console: 'readonly', + Buffer: 'readonly', + global: 'readonly', + URL: 'readonly', + TextEncoder: 'readonly', + TextDecoder: 'readonly', + }, + }, + rules: { + 'no-console': 'off', + }, + }, + + { + // The ESM half of the block above. A `scripts/*.mjs` is genuinely a module + // and must keep the default `sourceType`, so it gets Node's globals but + // none of the CommonJS wrapper. Measured: `process` reported undefined 2x + // in hermiq's generate-opengemeenten-icons.mjs and 4x in openregister's + // l10n/runtime-check.mjs, which the `.js`/`.cjs` block deliberately does + // not match. + files: ['scripts/**/*.mjs', 'tests/**/*.mjs'], + languageOptions: { + globals: { + process: 'readonly', + console: 'readonly', + Buffer: 'readonly', + global: 'readonly', + URL: 'readonly', + TextEncoder: 'readonly', + TextDecoder: 'readonly', + }, + }, + rules: { + 'no-console': 'off', + }, + }, + + { + // eslint must not try to PARSE a shell script. `tests/e2e/seed.test.sh` + // matches the `**/*.test.*` glob some presets use, and eslint then reads + // it as JavaScript and reports "Parsing error: Unexpected character" β€” + // a finding about a file it should never have opened. + ignores: ['**/*.sh', '**/*.bash'], + }, + + // eslint-config-prettier LAST OF THE PRESETS, and it has to be: it only turns // rules OFF, and what it turns off is everything prettier owns β€” including the // `@stylistic/*` family v9 introduces (`indent`, `quotes`, `semi`). diff --git a/package.json b/package.json index e467d218b..18b82eb40 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "build": "NODE_ENV=production webpack --config webpack.config.js --progress", "dev": "NODE_ENV=development webpack --config webpack.config.js --progress", "watch": "NODE_ENV=development webpack --config webpack.config.js --progress --watch", - "lint": "eslint src", + "lint": "eslint src tests scripts", "lint-fix": "npm run lint -- --fix", "test": "vitest run", "test-coverage": "vitest run --coverage", diff --git a/scripts/build-l10n-js.js b/scripts/build-l10n-js.js index 176ef519c..335b08d2c 100644 --- a/scripts/build-l10n-js.js +++ b/scripts/build-l10n-js.js @@ -101,6 +101,9 @@ function renderJs(id, translations, pluralForm) { ].join('\n') } +/** + * + */ function main() { const check = process.argv.includes('--check') const id = appId() diff --git a/scripts/check-integration-parity.js b/scripts/check-integration-parity.js index 0c44a7320..b74516fae 100755 --- a/scripts/check-integration-parity.js +++ b/scripts/check-integration-parity.js @@ -10,11 +10,11 @@ * --------------------------------------- * A leaf has TWO faces (ADR-019 AD-11/AD-13, ADR-066 decisions 4 and 7): * - * * a SERVER face β€” either a `LeafDescriptor` contributed through + * a SERVER face β€” either a `LeafDescriptor` contributed through * `RegisterLeafProvidersEvent`, or an `IntegrationProvider` registered on * OpenRegister's `IntegrationRegistry`. This face is what the * `openregister.integrations.leaves` capability advertises. - * * a JS face β€” a `registerIntegration({ id, … })` call that mounts the + * a JS face β€” a `registerIntegration({ id, … })` call that mounts the * render pair on `window.OCA.OpenRegister.integrations`. * * The two are correlated ONLY by a shared `id`. Nothing at runtime notices when @@ -132,7 +132,7 @@ function collectFiles(root, test, maxDepth = 10) { let entries try { entries = fs.readdirSync(dir, { withFileTypes: true }) - } catch (e) { + } catch { return } for (const ent of entries) { @@ -289,17 +289,17 @@ function resolvePhp(expr, localConsts, globalConsts) { } m = /^(?:self|static)::([A-Z0-9_]+)$/.exec(e) if (m !== null) { - return Object.prototype.hasOwnProperty.call(localConsts, m[1]) + return Object.hasOwn(localConsts, m[1]) ? localConsts[m[1]] : null } m = /^([A-Za-z_][A-Za-z0-9_]*)::([A-Z0-9_]+)$/.exec(e) if (m !== null) { const key = `${m[1]}::${m[2]}` - if (Object.prototype.hasOwnProperty.call(FOREIGN_CONSTANTS, key) === true) { + if (Object.hasOwn(FOREIGN_CONSTANTS, key) === true) { return FOREIGN_CONSTANTS[key] } - return Object.prototype.hasOwnProperty.call(globalConsts, key) + return Object.hasOwn(globalConsts, key) ? globalConsts[key] : null } @@ -351,8 +351,8 @@ function phpLocalConsts(src) { * * Two shapes count as a server face, because both are how a leaf reaches the * `openregister.integrations.leaves` capability: - * * `new LeafDescriptor(…)` β€” the ADR-066 collect-event contribution; - * * an `IntegrationProvider` (extends `AbstractIntegrationProvider` or + * `new LeafDescriptor(…)` β€” the ADR-066 collect-event contribution; + * an `IntegrationProvider` (extends `AbstractIntegrationProvider` or * implements `IntegrationProviderInterface`) whose `getId()` returns a * literal β€” the `IntegrationRegistry::addProvider()` path. * @@ -371,7 +371,7 @@ function collectServerFaces() { let src try { src = fs.readFileSync(file, 'utf8') - } catch (e) { + } catch { continue } sources.set(file, src) @@ -504,7 +504,7 @@ function resolveJs(expr, locals) { } if ( /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(e) === true - && Object.prototype.hasOwnProperty.call(locals, e) === true + && Object.hasOwn(locals, e) === true ) { return locals[e] } @@ -588,7 +588,7 @@ function collectJsRegistrations() { let src try { src = fs.readFileSync(file, 'utf8') - } catch (e) { + } catch { continue } // BOTH SUPPORTED REGISTRATION APIs, NOT ONE. @@ -737,7 +737,7 @@ function collectOrSchemas() { let doc try { doc = JSON.parse(fs.readFileSync(file, 'utf8')) - } catch (e) { + } catch { continue } const declared = doc && doc.components && doc.components.schemas @@ -959,7 +959,7 @@ function main() { continue } counts.R6++ - if (Object.prototype.hasOwnProperty.call(schemas, slug) === false) { + if (Object.hasOwn(schemas, slug) === false) { failures.push( `βœ— [R6 offlineConfig] leaf offlineConfig.${key} = "${slug}" (${r.file}) names a ` + `schema this repo does not declare in lib/Settings/** β€” the leaf would query a ` @@ -996,12 +996,12 @@ function main() { .map(([rule, n]) => `${rule}:${n}`) .join(' ') if (failures.length === 0) { - // eslint-disable-next-line no-console + console.log( `βœ“ integration parity: ${scope} β€” all rules pass (assertions run per rule: ${perRule})`, ) if (Object.values(counts).every((n) => n === 0) === true) { - // eslint-disable-next-line no-console + console.error( 'βœ— integration parity: every rule had ZERO subject matter, yet gate-24 selected this ' + 'repo as one that registers leaves. That contradiction means this checker failed to ' @@ -1014,15 +1014,15 @@ function main() { // The header carries no `βœ—` on purpose: gate-24 counts violations by // grepping `^βœ—` in this log, so every violation β€” and only a violation β€” // starts a line with it. - // eslint-disable-next-line no-console + console.error( `integration parity gate FAILED β€” ${failures.length} violation(s) over ${scope}:`, ) for (const f of failures) { - // eslint-disable-next-line no-console + console.error(f) } - // eslint-disable-next-line no-console + console.error(`\nAssertions run per rule: ${perRule}`) process.exit(1) } diff --git a/scripts/check-l10n.js b/scripts/check-l10n.js index d87201a86..575561212 100755 --- a/scripts/check-l10n.js +++ b/scripts/check-l10n.js @@ -1,8 +1,6 @@ #!/usr/bin/env node /* eslint-disable jsdoc/require-param */ -/* eslint-disable n/no-process-exit */ -/* eslint-disable no-console */ -/* eslint-disable n/shebang */ + /** * l10n/i18n consistency checker. * @@ -19,7 +17,6 @@ const fs = require('fs') const path = require('path') - const { loadJsTranslations, walk, @@ -40,6 +37,9 @@ const DIM = '\x1b[2m' const BOLD = '\x1b[1m' const RESET = '\x1b[0m' +/** + * + */ function rel(p) { return path.relative(ROOT, p) } @@ -106,6 +106,9 @@ const NON_DISPLAY_ATTRS = new Set([ 'back-route', // Vue Router route name passed to $router.push({ name }) ]) +/** + * + */ function findUnwrapped(vueFiles, keys) { const hits = [] for (const file of vueFiles) { @@ -166,12 +169,18 @@ function findUnwrapped(vueFiles, keys) { return hits } +/** + * + */ function printSection(title, color, body) { console.log(`${color}${BOLD}${title}${RESET}`) console.log(body) console.log('') } +/** + * + */ function main() { const { app, translations } = loadJsTranslations(L10N_FILE) const keys = new Set(Object.keys(translations)) diff --git a/scripts/check-schema-l10n.js b/scripts/check-schema-l10n.js index 8a860b330..3c4b2626f 100644 --- a/scripts/check-schema-l10n.js +++ b/scripts/check-schema-l10n.js @@ -113,6 +113,9 @@ function collect(node, where, sink) { for (const value of Object.values(node)) collect(value, where, sink) } +/** + * + */ function main() { const update = process.argv.includes('--update') const list = process.argv.includes('--list') diff --git a/scripts/clean-l10n.js b/scripts/clean-l10n.js index 226acb9d4..f835da042 100755 --- a/scripts/clean-l10n.js +++ b/scripts/clean-l10n.js @@ -1,8 +1,6 @@ #!/usr/bin/env node -/* eslint-disable jsdoc/require-param */ -/* eslint-disable n/no-process-exit */ -/* eslint-disable no-console */ -/* eslint-disable n/shebang */ + + /** * l10n unused-key remover. * @@ -29,7 +27,6 @@ const fs = require('fs') const path = require('path') - const { loadJsTranslations, serializeJs, @@ -54,6 +51,9 @@ const apply = args.has('--apply') // ---------- Main ---------- +/** + * + */ function main() { if (!fs.existsSync(ENGLISH_FILE)) { console.error(`English source file not found: ${ENGLISH_FILE}`) diff --git a/scripts/find-unwrapped.js b/scripts/find-unwrapped.js index 4da6572c5..7a1c663ef 100644 --- a/scripts/find-unwrapped.js +++ b/scripts/find-unwrapped.js @@ -1,8 +1,6 @@ #!/usr/bin/env node /* eslint-disable jsdoc/require-param */ -/* eslint-disable n/no-process-exit */ -/* eslint-disable no-console */ -/* eslint-disable n/shebang */ + /** * Candidate unwrapped-string detector. * @@ -28,7 +26,6 @@ const fs = require('fs') const path = require('path') - const { walk } = require('./lib/l10n.js') const ROOT = path.resolve(__dirname, '..') @@ -111,6 +108,9 @@ function isComponentAttrOptOut(tagName, attrName) { // ---------- CLI ---------- +/** + * + */ function parseFlags(argv) { const flags = {} const positionals = [] @@ -142,6 +142,9 @@ const minLength = flags['min-length'] ? Math.max(1, parseInt(flags['min-length'] // ---------- helpers ---------- +/** + * + */ function rel(p) { return path.relative(ROOT, p) } @@ -305,6 +308,9 @@ function computeTCallRanges(text, app) { return ranges } +/** + * + */ function isInsideRange(pos, ranges) { for (const [start, end] of ranges) { if (pos >= start && pos < end) return true @@ -447,6 +453,9 @@ function isInsideStoreCall(expr, pos) { return /Store$/.test(baseIdent) } +/** + * + */ function findStringLiteralsInExpression(expr) { const out = [] let i = 0 @@ -716,6 +725,9 @@ function scanScript(file, fullText, scriptStart, scriptEnd, tCallRanges) { // ---------- main ---------- +/** + * + */ function findVueFiles(roots) { const files = [] for (const root of roots) { @@ -756,6 +768,9 @@ function detectAppName() { process.exit(2) } +/** + * + */ function main() { const roots = positionals.length ? positionals.map((p) => path.isAbsolute(p) ? p : path.join(process.cwd(), p)) diff --git a/scripts/l10n-ai.js b/scripts/l10n-ai.js index e1dba92ac..f20ffe098 100644 --- a/scripts/l10n-ai.js +++ b/scripts/l10n-ai.js @@ -1,8 +1,6 @@ #!/usr/bin/env node /* eslint-disable jsdoc/require-param */ -/* eslint-disable n/no-process-exit */ -/* eslint-disable no-console */ -/* eslint-disable n/shebang */ + /** * AI-focused l10n CRUD tool. Designed to be invoked one subcommand at a time * by Claude (or other automation) so individual operations stay cheap in @@ -31,7 +29,6 @@ const fs = require('fs') const path = require('path') - const { loadJsTranslations, serializeJs, @@ -115,6 +112,9 @@ function parseValuePairs(pairs) { // ---------- file helpers ---------- +/** + * + */ function loadAll() { const files = listJsLocaleFiles(L10N_DIR) if (!files.length) { @@ -147,17 +147,26 @@ function writeAll(entries) { } } +/** + * + */ function fail(msg, code = 1) { console.error(msg) process.exit(code) } +/** + * + */ function rel(p) { return path.relative(ROOT, p) } // ---------- subcommands ---------- +/** + * + */ function cmdHas(args) { const { positionals, flags } = parseArgs(args) const [key] = positionals @@ -187,6 +196,9 @@ function cmdHas(args) { } } +/** + * + */ function cmdGet(args) { const { positionals } = parseArgs(args) const [key] = positionals @@ -195,7 +207,7 @@ function cmdGet(args) { const entries = loadAll() let any = false for (const e of entries) { - if (Object.prototype.hasOwnProperty.call(e.translations, key)) { + if (Object.hasOwn(e.translations, key)) { any = true const v = e.translations[key] const out = Array.isArray(v) ? JSON.stringify(v) : v @@ -208,6 +220,9 @@ function cmdGet(args) { } } +/** + * + */ function cmdFind(args) { const { positionals } = parseArgs(args) const [substring] = positionals @@ -230,6 +245,9 @@ function cmdFind(args) { } } +/** + * + */ function cmdAdd(args) { const { positionals, opts, flags } = parseArgs(args, { repeatable: new Set(['value']) }) const [key] = positionals @@ -276,7 +294,7 @@ function cmdAdd(args) { const existing = [] for (const e of entries) { if (!targetLocales.has(e.locale)) continue - if (Object.prototype.hasOwnProperty.call(e.translations, key)) { + if (Object.hasOwn(e.translations, key)) { existing.push(e.locale) } } @@ -295,6 +313,9 @@ function cmdAdd(args) { for (const e of toWrite) console.log(`${e.locale}.js\t${valueMap[e.locale]}`) } +/** + * + */ function cmdSet(args) { const { positionals, opts } = parseArgs(args) const [key] = positionals @@ -307,7 +328,7 @@ function cmdSet(args) { if (!target) { fail(`set: locale '${opts.locale}' has no l10n/${opts.locale}.js (known: ${entries.map((e) => e.locale).join(', ')})`) } - if (!Object.prototype.hasOwnProperty.call(target.translations, key)) { + if (!Object.hasOwn(target.translations, key)) { fail(`set: key '${key}' not present in ${opts.locale}.js. Use 'add' first.`) } if (Array.isArray(target.translations[key])) { @@ -319,13 +340,16 @@ function cmdSet(args) { console.log(`${target.locale}.js\t${opts.value}`) } +/** + * + */ function cmdRm(args) { const { positionals, flags } = parseArgs(args) const [key] = positionals if (!key) fail('usage: rm [--force]') const entries = loadAll() - const present = entries.filter((e) => Object.prototype.hasOwnProperty.call(e.translations, key)) + const present = entries.filter((e) => Object.hasOwn(e.translations, key)) if (!present.length) { fail(`rm: key '${key}' not found in any locale .js file`) } @@ -350,6 +374,9 @@ function cmdRm(args) { for (const e of toWrite) console.log(`${e.locale}.js\tremoved`) } +/** + * + */ function cmdRename(args) { const { positionals, flags } = parseArgs(args) const [oldKey, newKey] = positionals @@ -357,18 +384,18 @@ function cmdRename(args) { if (oldKey === newKey) fail('rename: old and new keys are identical') const entries = loadAll() - const present = entries.filter((e) => Object.prototype.hasOwnProperty.call(e.translations, oldKey)) + const present = entries.filter((e) => Object.hasOwn(e.translations, oldKey)) if (!present.length) { fail(`rename: key '${oldKey}' not found in any locale .js file`) } - const collisions = entries.filter((e) => Object.prototype.hasOwnProperty.call(e.translations, newKey)) + const collisions = entries.filter((e) => Object.hasOwn(e.translations, newKey)) if (collisions.length && !flags.force) { fail(`rename: target key '${newKey}' already exists in ${collisions.map((e) => e.locale + '.js').join(', ')}. Pass --force to overwrite.`) } const toWrite = [] for (const e of entries) { - if (!Object.prototype.hasOwnProperty.call(e.translations, oldKey)) continue + if (!Object.hasOwn(e.translations, oldKey)) continue const next = { ...e.translations } next[newKey] = next[oldKey] delete next[oldKey] @@ -378,12 +405,18 @@ function cmdRename(args) { for (const e of toWrite) console.log(`${e.locale}.js\trenamed`) } +/** + * + */ function cmdListLocales() { const files = listJsLocaleFiles(L10N_DIR) if (!files.length) fail('list-locales: no l10n/*.js files found') for (const f of files) console.log(localeNameOf(f)) } +/** + * + */ function cmdHelp() { const text = [ 'Usage: node scripts/l10n-ai.js [args...]', @@ -407,6 +440,9 @@ function cmdHelp() { // ---------- main ---------- +/** + * + */ function main() { const [, , sub, ...rest] = process.argv if (!sub || sub === '--help' || sub === '-h') { diff --git a/scripts/lib/l10n.js b/scripts/lib/l10n.js index efeec37ae..1bad1ca20 100644 --- a/scripts/lib/l10n.js +++ b/scripts/lib/l10n.js @@ -313,6 +313,9 @@ function findKeyReferences(srcDir, app, key) { return hits } +/** + * + */ function escapeRegex(s) { return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') } diff --git a/tests/e2e/api-direct/configuration-export-import.spec.ts b/tests/e2e/api-direct/configuration-export-import.spec.ts index f3a009c7e..d2e2fc32c 100644 --- a/tests/e2e/api-direct/configuration-export-import.spec.ts +++ b/tests/e2e/api-direct/configuration-export-import.spec.ts @@ -19,7 +19,9 @@ * - The /import page contains a meaningful form */ -import { test, expect, type Page } from '@playwright/test' +import type { Page } from '@playwright/test' + +import { expect, test } from '@playwright/test' const OR_BASE = '/index.php/apps/openregister/api' diff --git a/tests/e2e/api-direct/consumer-rate-limiting.api.spec.ts b/tests/e2e/api-direct/consumer-rate-limiting.api.spec.ts index b64d43e3a..34f0d6e44 100644 --- a/tests/e2e/api-direct/consumer-rate-limiting.api.spec.ts +++ b/tests/e2e/api-direct/consumer-rate-limiting.api.spec.ts @@ -23,7 +23,7 @@ * prerequisite is why the live assertion is deferred rather than run here. */ -import { test, expect } from '@playwright/test' +import { expect, test } from '@playwright/test' const API_BASE = '/index.php/apps/integriq/api' diff --git a/tests/e2e/api-direct/endpoint-runtime.api.spec.ts b/tests/e2e/api-direct/endpoint-runtime.api.spec.ts index c8d7d0f41..585018790 100644 --- a/tests/e2e/api-direct/endpoint-runtime.api.spec.ts +++ b/tests/e2e/api-direct/endpoint-runtime.api.spec.ts @@ -21,7 +21,7 @@ * fixed: synchronizations now resolve through OpenRegister. */ -import { test, expect } from '@playwright/test' +import { expect, test } from '@playwright/test' const API_BASE = '/index.php/apps/integriq/api' const OR_BASE = '/index.php/apps/openregister/api/objects/integriq' diff --git a/tests/e2e/api-direct/rule-pipeline.api.spec.ts b/tests/e2e/api-direct/rule-pipeline.api.spec.ts index 514840163..819dcd6f6 100644 --- a/tests/e2e/api-direct/rule-pipeline.api.spec.ts +++ b/tests/e2e/api-direct/rule-pipeline.api.spec.ts @@ -17,7 +17,7 @@ * OpenRegister. */ -import { test, expect } from '@playwright/test' +import { expect, test } from '@playwright/test' const OR_BASE = '/index.php/apps/openregister/api/objects/integriq' const API_BASE = '/index.php/apps/integriq/api' diff --git a/tests/e2e/api-direct/synchronization-engine.api.spec.ts b/tests/e2e/api-direct/synchronization-engine.api.spec.ts index c776e1721..23c196cd1 100644 --- a/tests/e2e/api-direct/synchronization-engine.api.spec.ts +++ b/tests/e2e/api-direct/synchronization-engine.api.spec.ts @@ -18,7 +18,7 @@ * schema `synchronization`). */ -import { test, expect } from '@playwright/test' +import { expect, test } from '@playwright/test' const OR_BASE = '/index.php/apps/openregister/api/objects/integriq' const API_BASE = '/index.php/apps/integriq/api' diff --git a/tests/e2e/api-direct/user-management.spec.ts b/tests/e2e/api-direct/user-management.spec.ts index 4ba0ee765..0ecd657e5 100644 --- a/tests/e2e/api-direct/user-management.spec.ts +++ b/tests/e2e/api-direct/user-management.spec.ts @@ -12,9 +12,9 @@ * storageState. */ -import { test, expect } from '@playwright/test' +import { expect, test } from '@playwright/test' import * as http from 'http' -import { BASE_URL, baseUrlParts } from '../support/baseUrl' +import { BASE_URL, baseUrlParts } from '../support/baseUrl.ts' const BASE = BASE_URL const ME_URL = '/index.php/apps/integriq/api/user/me' diff --git a/tests/e2e/docs-screenshots.spec.ts b/tests/e2e/docs-screenshots.spec.ts index 3cc316cbd..663e1eca6 100644 --- a/tests/e2e/docs-screenshots.spec.ts +++ b/tests/e2e/docs-screenshots.spec.ts @@ -50,10 +50,12 @@ * Pattern reference: ADR-030 (hydra/openspec/architecture/). */ -import { test, expect, type Page } from '@playwright/test' +import type { Page } from '@playwright/test' + import { dismissFirstVisitOverlays } from '@conduction/nextcloud-vue/testing/playwright' -import * as path from 'path' +import { expect, test } from '@playwright/test' import * as fs from 'fs' +import * as path from 'path' const SHOT_ROOT = path.resolve( __dirname, diff --git a/tests/e2e/global-setup.ts b/tests/e2e/global-setup.ts index fdd48f6aa..68c916e24 100644 --- a/tests/e2e/global-setup.ts +++ b/tests/e2e/global-setup.ts @@ -19,12 +19,14 @@ * from decidesk's journeydoc setup. */ -import { chromium, request, type FullConfig } from '@playwright/test' +import type { FullConfig } from '@playwright/test' + +import { seedFirstVisitOverlaysSeen } from '@conduction/nextcloud-vue/testing/playwright' +import { chromium, request } from '@playwright/test' import { execSync } from 'child_process' -import * as path from 'path' import * as fs from 'fs' -import { BASE_URL } from './support/baseUrl' -import { seedFirstVisitOverlaysSeen } from '@conduction/nextcloud-vue/testing/playwright' +import * as path from 'path' +import { BASE_URL } from './support/baseUrl.ts' const AUTH_DIR = path.resolve(__dirname, '.auth') const STORAGE_STATE = path.join(AUTH_DIR, 'admin.json') @@ -48,7 +50,7 @@ function ensureBundleBuilt(): void { if (fs.existsSync(BUNDLE_PATH)) { return } - // eslint-disable-next-line no-console + console.log( `[playwright globalSetup] bundle missing at ${BUNDLE_PATH}; running 'npm run build' once…`, ) @@ -192,12 +194,11 @@ export default async function globalSetup(config: FullConfig): Promise { headers: { requesttoken: token }, }) return res.status - } catch (e) { + } catch { return -1 } }) if (wizardStatus !== 200 && wizardStatus !== 404) { - // eslint-disable-next-line no-console console.warn( `[playwright globalSetup] first-run wizard dismissal returned ${wizardStatus}; ` + 'specs may hit an overlay that blocks clicks without hiding anything.', diff --git a/tests/e2e/playwright.config.ts b/tests/e2e/playwright.config.ts index 2f29b96ad..f547658f4 100644 --- a/tests/e2e/playwright.config.ts +++ b/tests/e2e/playwright.config.ts @@ -80,8 +80,7 @@ import { defineConfig, devices } from '@playwright/test' import * as path from 'path' - -import { BASE_URL } from './support/baseUrl' +import { BASE_URL } from './support/baseUrl.ts' const APP_ROOT = path.resolve(__dirname, '..', '..') diff --git a/tests/e2e/regression/dead-letter-replay.spec.ts b/tests/e2e/regression/dead-letter-replay.spec.ts index 693e3d2cb..1b520caa6 100644 --- a/tests/e2e/regression/dead-letter-replay.spec.ts +++ b/tests/e2e/regression/dead-letter-replay.spec.ts @@ -54,8 +54,10 @@ * - lib/Service/EventService.php */ -import { test, expect, type Page, type ConsoleMessage } from '@playwright/test' -import { gotoAppRoute, expectRouteMatched } from '../support/appRoot' +import type { ConsoleMessage, Page } from '@playwright/test' + +import { expect, test } from '@playwright/test' +import { expectRouteMatched, gotoAppRoute } from '../support/appRoot.ts' /** * The route that actually mounts `EventDeliveriesPage` (ADR-080 merge). diff --git a/tests/e2e/regression/dead-letters-merged.spec.ts b/tests/e2e/regression/dead-letters-merged.spec.ts index 005aff311..2459a094c 100644 --- a/tests/e2e/regression/dead-letters-merged.spec.ts +++ b/tests/e2e/regression/dead-letters-merged.spec.ts @@ -30,8 +30,8 @@ * - src/views/Synchronization/SyncDeadLetterPage.vue */ -import { test, expect } from '@playwright/test' -import { gotoAppRoute, expectRouteMatched } from '../support/appRoot' +import { expect, test } from '@playwright/test' +import { expectRouteMatched, gotoAppRoute } from '../support/appRoot.ts' /** * The merged operations surface. diff --git a/tests/e2e/regression/journeys.spec.ts b/tests/e2e/regression/journeys.spec.ts index f70ae29b1..b37b981c2 100644 --- a/tests/e2e/regression/journeys.spec.ts +++ b/tests/e2e/regression/journeys.spec.ts @@ -32,14 +32,12 @@ * OR backend, so the suite is end-to-end UI-driven. */ -import { test, expect, Page } from '@playwright/test' -import { BASE_URL } from '../support/baseUrl' -import { appDialog } from '../support/dialogs' -import { resolveAppRoot, expectRouteMatched } from '../support/appRoot' +import type { Locator } from '@playwright/test' +import type { Page } from '@playwright/test' -const NEXTCLOUD = BASE_URL -const ADMIN_USER = process.env.NC_ADMIN_USER || 'admin' -const ADMIN_PASS = process.env.NC_ADMIN_PASS || 'admin' +import { expect, test } from '@playwright/test' +import { expectRouteMatched, resolveAppRoot } from '../support/appRoot.ts' +import { appDialog } from '../support/dialogs.ts' const OR = '/index.php/apps/openregister/api/objects/integriq' @@ -504,7 +502,7 @@ async function deleteViaUi( // still uses checkboxes. const row = page.getByRole('row', { name: new RegExp(name) }).first() const rowVisible = await row.isVisible().catch(() => false) - let rowCheckbox: import('@playwright/test').Locator + let rowCheckbox: Locator if (rowVisible) { rowCheckbox = row.getByRole('checkbox').first() } else { @@ -617,7 +615,7 @@ async function editViaUi( // CnRowActions/CnCardItem renders an overflow-actions NcActions button. const row = page.getByRole('row', { name: new RegExp(name) }).first() const rowVisible = await row.isVisible().catch(() => false) - let actionsBtn: import('@playwright/test').Locator + let actionsBtn: Locator if (rowVisible) { actionsBtn = row.getByRole('button', { name: /Actions/i }).first() } else { @@ -755,7 +753,7 @@ async function singleDeleteViaUi(page: Page, schemaSlug: string, name: string) { // Find and click the Actions button near the item name. const row = page.getByRole('row', { name: new RegExp(name) }).first() const rowVisible = await row.isVisible().catch(() => false) - let actionsBtn: import('@playwright/test').Locator + let actionsBtn: Locator if (rowVisible) { actionsBtn = row.getByRole('button', { name: /Actions/i }).first() } else { diff --git a/tests/e2e/regression/manifest-pages.spec.ts b/tests/e2e/regression/manifest-pages.spec.ts index 86cb7490b..0a3a42a91 100644 --- a/tests/e2e/regression/manifest-pages.spec.ts +++ b/tests/e2e/regression/manifest-pages.spec.ts @@ -43,8 +43,10 @@ * - src/manifest.json */ -import { test, expect, type Page, type ConsoleMessage } from '@playwright/test' +import type { Page } from '@playwright/test' +import type { ConsoleMessage, Page } from '@playwright/test' +import { expect, test } from '@playwright/test' /* * SCENARIOS THIS FILE PROVES. * @@ -103,7 +105,6 @@ import { test, expect, type Page, type ConsoleMessage } from '@playwright/test' * β€” the manifest type and the mount are proven; that widget counts resolve * via dataSource blocks against OR's aggregate endpoint is not. */ - // In Nextcloud installs with `htaccess.RewriteBase => '/'` (the // default for the apache-served dev container) `generateUrl` returns // `/apps/integriq` and the Vue Router's `base` is set to that β€” @@ -124,9 +125,9 @@ import { test, expect, type Page, type ConsoleMessage } from '@playwright/test' // Resolution now comes from `OC.generateUrl` β€” the function src/main.js itself // calls to build the router base β€” and each test asserts the router MATCHED // before looking at anything. -import { resolveAppRoot, expectRouteMatched } from '../support/appRoot' +import { expectRouteMatched, resolveAppRoot } from '../support/appRoot.ts' -async function rootUrl(page: import('@playwright/test').Page): Promise { +async function rootUrl(page: Page): Promise { return await resolveAppRoot(page) } @@ -364,11 +365,16 @@ test.describe('manifest pages β€” schema-driven render', () => { }) test.describe('manifest schema validation', () => { + // This suite compiles as CommonJS, so `import.meta` is a syntax error and + // `require` is how it reaches the filesystem. The directives below say so + // at each site; the reason is here. function readManifest(): Record { + // eslint-disable-next-line @typescript-eslint/no-require-imports const manifestPath = require('path').resolve( __dirname, '../../../src/manifest.json', ) + // eslint-disable-next-line @typescript-eslint/no-require-imports return JSON.parse(require('fs').readFileSync(manifestPath, 'utf-8')) } @@ -378,19 +384,23 @@ test.describe('manifest schema validation', () => { // fails it as an error with no statement of intent β€” and a reader // checking whether "the manifest exists and parses" is covered cannot // see an assertion that isn't written down. + // eslint-disable-next-line @typescript-eslint/no-require-imports const manifestPath = require('path').resolve( __dirname, '../../../src/manifest.json', ) expect( + // eslint-disable-next-line @typescript-eslint/no-require-imports require('fs').existsSync(manifestPath), `manifest.json must exist at ${manifestPath}`, ).toBe(true) expect( + // eslint-disable-next-line @typescript-eslint/no-require-imports require('fs').statSync(manifestPath).isFile(), 'manifest.json must be a regular file', ).toBe(true) expect( + // eslint-disable-next-line @typescript-eslint/no-require-imports () => JSON.parse(require('fs').readFileSync(manifestPath, 'utf-8')), 'manifest.json must parse as valid JSON with no syntax errors', ).not.toThrow() diff --git a/tests/e2e/regression/migration-round-trip.spec.ts b/tests/e2e/regression/migration-round-trip.spec.ts index 69fcd51eb..ebe86c97f 100644 --- a/tests/e2e/regression/migration-round-trip.spec.ts +++ b/tests/e2e/regression/migration-round-trip.spec.ts @@ -32,13 +32,10 @@ * synced-from-leaf.spec.ts) rather than producing a false failure. */ -import { - test, - expect, - request as pwRequest, - APIRequestContext, -} from '@playwright/test' -import { BASE_URL } from '../support/baseUrl' +import type { APIRequestContext } from '@playwright/test' + +import { expect, request as pwRequest, test } from '@playwright/test' +import { BASE_URL } from '../support/baseUrl.ts' const NEXTCLOUD = BASE_URL const ADMIN_USER = process.env.NC_ADMIN_USER || 'admin' @@ -110,7 +107,6 @@ async function readStorageMigrated(ctx: APIRequestContext): Promise { .catch(() => null) if (res === null) { - // eslint-disable-next-line no-console console.warn('[migration-round-trip] storage_migrated probe: request threw') return false } @@ -119,7 +115,6 @@ async function readStorageMigrated(ctx: APIRequestContext): Promise { const ocsStatus = body?.ocs?.meta?.statuscode const value = body?.ocs?.data?.data - // eslint-disable-next-line no-console console.info( `[migration-round-trip] storage_migrated probe: HTTP ${res.status()},` + ` ocs.meta.statuscode=${String(ocsStatus)}, value=${JSON.stringify(value)}`, diff --git a/tests/e2e/regression/spa-root-resolution.spec.ts b/tests/e2e/regression/spa-root-resolution.spec.ts index 89f504237..70f48f80a 100644 --- a/tests/e2e/regression/spa-root-resolution.spec.ts +++ b/tests/e2e/regression/spa-root-resolution.spec.ts @@ -28,8 +28,12 @@ * would be worthless in exactly the environment it was written for. */ -import { test, expect } from '@playwright/test' -import { resolveAppRoot, gotoAppRoute, expectRouteMatched } from '../support/appRoot' +import { expect, test } from '@playwright/test' +import { + expectRouteMatched, + gotoAppRoute, + resolveAppRoot, +} from '../support/appRoot.ts' /** A route that exists in `src/manifest.json` and needs no fixture data. */ const ROUTE = '/sources' diff --git a/tests/e2e/regression/synced-from-leaf.spec.ts b/tests/e2e/regression/synced-from-leaf.spec.ts index 46ba4f977..eea932ef8 100644 --- a/tests/e2e/regression/synced-from-leaf.spec.ts +++ b/tests/e2e/regression/synced-from-leaf.spec.ts @@ -29,13 +29,10 @@ * suite skips (not fails) when that flag isn't set on the instance. */ -import { - test, - expect, - request as pwRequest, - type APIRequestContext, -} from '@playwright/test' -import { BASE_URL } from '../support/baseUrl' +import type { APIRequestContext } from '@playwright/test' + +import { expect, request as pwRequest, test } from '@playwright/test' +import { BASE_URL } from '../support/baseUrl.ts' const NEXTCLOUD = BASE_URL const ADMIN_USER = process.env.NC_ADMIN_USER || 'admin' @@ -178,7 +175,6 @@ test.describe('Synced-from leaf β€” contract provenance on objects', () => { } if (!storageMigrated) { - // eslint-disable-next-line no-console console.warn(`[synced-from-leaf] skipping: ${disabledReason}`) skipReason = disabledReason return diff --git a/tests/e2e/regression/webhook-signing.spec.ts b/tests/e2e/regression/webhook-signing.spec.ts index f4e87fba1..c3d2baded 100644 --- a/tests/e2e/regression/webhook-signing.spec.ts +++ b/tests/e2e/regression/webhook-signing.spec.ts @@ -57,8 +57,10 @@ * - lib/Controller/EventsController.php */ -import { test, expect, type Page, type ConsoleMessage } from '@playwright/test' -import { gotoAppRoute, expectRouteMatched } from '../support/appRoot' +import type { ConsoleMessage, Page } from '@playwright/test' + +import { expect, test } from '@playwright/test' +import { expectRouteMatched, gotoAppRoute } from '../support/appRoot.ts' // The candidate-probe that used to live here always returned the FIRST prefix, // because Nextcloud serves the identical SPA shell under both β€” so on CI these diff --git a/tests/e2e/spec-coverage/_helpers.ts b/tests/e2e/spec-coverage/_helpers.ts index 14592a36f..c3202fe4c 100644 --- a/tests/e2e/spec-coverage/_helpers.ts +++ b/tests/e2e/spec-coverage/_helpers.ts @@ -1,3 +1,5 @@ +import type { Page } from '@playwright/test' + /* * SPDX-FileCopyrightText: 2026 Conduction B.V. * SPDX-License-Identifier: EUPL-1.2 @@ -11,8 +13,8 @@ * so console-error / 500 assertions only fail on integriq-origin * problems. */ -import { type Page, expect } from '@playwright/test' -import { appDialog } from '../support/dialogs' +import { expect } from '@playwright/test' +import { appDialog } from '../support/dialogs.ts' // The one integriq URL base for the whole spec-coverage suite. Two // separate things are encoded here, and both were learned from a failing run. diff --git a/tests/e2e/spec-coverage/action-authorization.spec.ts b/tests/e2e/spec-coverage/action-authorization.spec.ts index 79df4cf45..fa155f987 100644 --- a/tests/e2e/spec-coverage/action-authorization.spec.ts +++ b/tests/e2e/spec-coverage/action-authorization.spec.ts @@ -12,7 +12,7 @@ * with their groups, and is served by the routes it claims. */ -import { test, expect } from '@playwright/test' +import { expect, test } from '@playwright/test' const MATRIX_URL = '/index.php/apps/integriq/api/admin/action-matrix' const ADMIN_SETTINGS_URL = '/index.php/settings/admin/integriq' diff --git a/tests/e2e/spec-coverage/api-product-gateway.spec.ts b/tests/e2e/spec-coverage/api-product-gateway.spec.ts index ba9fadfe6..823d70d15 100644 --- a/tests/e2e/spec-coverage/api-product-gateway.spec.ts +++ b/tests/e2e/spec-coverage/api-product-gateway.spec.ts @@ -24,7 +24,9 @@ * about, for the opposite reason. */ -import { test, expect, type Page } from '@playwright/test' +import type { Page } from '@playwright/test' + +import { expect, test } from '@playwright/test' const APP_BASE = '/index.php/apps/integriq' diff --git a/tests/e2e/spec-coverage/cloud-event-management.spec.ts b/tests/e2e/spec-coverage/cloud-event-management.spec.ts index 67c15325a..ad46d293d 100644 --- a/tests/e2e/spec-coverage/cloud-event-management.spec.ts +++ b/tests/e2e/spec-coverage/cloud-event-management.spec.ts @@ -17,14 +17,14 @@ * the main content area rather than relying on table cell content. */ -import { test, expect } from '@playwright/test' -import { appDialog } from '../support/dialogs' +import { expect, test } from '@playwright/test' +import { appDialog } from '../support/dialogs.ts' // APP_BASE comes from _helpers.ts, the one place that knows both that the // router is hash-mode and that the URL needs the `/index.php/` prefix (without // it, PHP's built-in server on CI 404s the app directory and every assertion // below runs against a 404 page). This file used to keep a private copy of // that string that was missing the prefix. -import { APP_BASE } from './_helpers' +import { APP_BASE } from './_helpers.ts' const OR_BASE = '/index.php/apps/openregister/api/objects/integriq' diff --git a/tests/e2e/spec-coverage/configuration-export-import.spec.ts b/tests/e2e/spec-coverage/configuration-export-import.spec.ts index ffd820c0c..28ab6ab85 100644 --- a/tests/e2e/spec-coverage/configuration-export-import.spec.ts +++ b/tests/e2e/spec-coverage/configuration-export-import.spec.ts @@ -19,7 +19,9 @@ * - The /import page contains a meaningful form */ -import { test, expect, type Page } from '@playwright/test' +import type { Page } from '@playwright/test' + +import { expect, test } from '@playwright/test' const OR_BASE = '/index.php/apps/openregister/api' diff --git a/tests/e2e/spec-coverage/configuration-import-export-ui.spec.ts b/tests/e2e/spec-coverage/configuration-import-export-ui.spec.ts index 536c72d3f..99d366502 100644 --- a/tests/e2e/spec-coverage/configuration-import-export-ui.spec.ts +++ b/tests/e2e/spec-coverage/configuration-import-export-ui.spec.ts @@ -17,7 +17,9 @@ * what the stand-down reason got right and what nobody had checked. */ -import { test, expect, type Page } from '@playwright/test' +import type { Page } from '@playwright/test' + +import { expect, test } from '@playwright/test' const APP_BASE = '/index.php/apps/integriq' diff --git a/tests/e2e/spec-coverage/connector-catalog.spec.ts b/tests/e2e/spec-coverage/connector-catalog.spec.ts index 92406890e..cdb630bf6 100644 --- a/tests/e2e/spec-coverage/connector-catalog.spec.ts +++ b/tests/e2e/spec-coverage/connector-catalog.spec.ts @@ -20,7 +20,7 @@ * against a provisioned instance. */ -import { test, expect } from '@playwright/test' +import { expect, test } from '@playwright/test' import * as fs from 'fs' import * as path from 'path' diff --git a/tests/e2e/spec-coverage/consumer-management.spec.ts b/tests/e2e/spec-coverage/consumer-management.spec.ts index ffdd61937..e3f89bdd4 100644 --- a/tests/e2e/spec-coverage/consumer-management.spec.ts +++ b/tests/e2e/spec-coverage/consumer-management.spec.ts @@ -20,14 +20,14 @@ * the main content area rather than relying on table cell content. */ -import { test, expect } from '@playwright/test' -import { appDialog } from '../support/dialogs' +import { expect, test } from '@playwright/test' +import { appDialog } from '../support/dialogs.ts' // APP_BASE comes from _helpers.ts, the one place that knows both that the // router is hash-mode and that the URL needs the `/index.php/` prefix (without // it, PHP's built-in server on CI 404s the app directory and every assertion // below runs against a 404 page). This file used to keep a private copy of // that string that was missing the prefix. -import { APP_BASE } from './_helpers' +import { APP_BASE } from './_helpers.ts' const OR_BASE = '/index.php/apps/openregister/api/objects/integriq' diff --git a/tests/e2e/spec-coverage/dashboard.spec.ts b/tests/e2e/spec-coverage/dashboard.spec.ts index addfaa0b5..3ddd16449 100644 --- a/tests/e2e/spec-coverage/dashboard.spec.ts +++ b/tests/e2e/spec-coverage/dashboard.spec.ts @@ -13,12 +13,12 @@ */ import { test } from '@playwright/test' import { - navTo, - trackErrors, + APP_BASE, assertNoAppErrors, expectHeading, - APP_BASE, -} from './_helpers' + navTo, + trackErrors, +} from './_helpers.ts' test.describe('Dashboard β€” index surface', () => { // @e2e openconnector-comprehensive-tests::dashboard-page-mounts diff --git a/tests/e2e/spec-coverage/dead-letters-ui.spec.ts b/tests/e2e/spec-coverage/dead-letters-ui.spec.ts index c94728565..03bea9257 100644 --- a/tests/e2e/spec-coverage/dead-letters-ui.spec.ts +++ b/tests/e2e/spec-coverage/dead-letters-ui.spec.ts @@ -1,3 +1,7 @@ +import type { Browser } from '@playwright/test' +import type { Page } from '@playwright/test' +import type { ApiClient } from '../workflows/_fixture.ts' + /* * SPDX-FileCopyrightText: 2026 Conduction B.V. * SPDX-License-Identifier: EUPL-1.2 @@ -26,14 +30,9 @@ * state) does NOT assume it got one from declaration order β€” it asks the * endpoint that feeds the view and skips if the queue is dirty. */ -import { test, expect, type Page } from '@playwright/test' -import { APP_BASE, trackErrors, assertNoAppErrors } from './_helpers' -import { - makeApiClient, - createObject, - deleteObject, - type ApiClient, -} from '../workflows/_fixture' +import { expect, test } from '@playwright/test' +import { createObject, deleteObject, makeApiClient } from '../workflows/_fixture.ts' +import { APP_BASE, assertNoAppErrors, trackErrors } from './_helpers.ts' /** Unique per-run marker so every assertion can scope to this run's rows. */ const runId = `dlui-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}` @@ -69,15 +68,12 @@ class Fixtures { api!: ApiClient private created: Array<{ schema: string; id: string }> = [] - async open( - browser: import('@playwright/test').Browser, - baseURL: string, - ): Promise { + async open(browser: Browser, baseURL: string): Promise { this.api = await makeApiClient(browser, baseURL) } /** Create an object and register it for teardown. */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any + async make(schema: string, data: Record): Promise { const obj = await createObject(this.api, schema, data) const id = obj.id ?? obj.uuid diff --git a/tests/e2e/spec-coverage/demo-data-setup-step.spec.ts b/tests/e2e/spec-coverage/demo-data-setup-step.spec.ts index d3ef1ed4c..84114ffc0 100644 --- a/tests/e2e/spec-coverage/demo-data-setup-step.spec.ts +++ b/tests/e2e/spec-coverage/demo-data-setup-step.spec.ts @@ -1,3 +1,5 @@ +import type { Page } from '@playwright/test' + /* * SPDX-FileCopyrightText: 2026 Conduction B.V. * SPDX-License-Identifier: EUPL-1.2 @@ -34,7 +36,7 @@ * * @spec exclude ADR-042/ADR-111 setup contract; no per-app behavioural spec. */ -import { test, expect, type Page } from '@playwright/test' +import { expect, test } from '@playwright/test' import * as path from 'path' const STORAGE_STATE = path.resolve(__dirname, '../.auth/admin.json') @@ -53,12 +55,12 @@ async function api( method, headers: { 'Content-Type': 'application/json', - // eslint-disable-next-line no-undef + requesttoken: (window as any).OC?.requestToken || '', 'OCS-APIREQUEST': 'true', }, }) - let json: any = null + let json: any try { json = await res.json() } catch { diff --git a/tests/e2e/spec-coverage/endpoint-runtime.spec.ts b/tests/e2e/spec-coverage/endpoint-runtime.spec.ts index 5063c1315..eeed58389 100644 --- a/tests/e2e/spec-coverage/endpoint-runtime.spec.ts +++ b/tests/e2e/spec-coverage/endpoint-runtime.spec.ts @@ -15,14 +15,14 @@ * the deep-link path). Always use the /apps/ prefix. */ -import { test, expect } from '@playwright/test' -import { appDialog } from '../support/dialogs' +import { expect, test } from '@playwright/test' +import { appDialog } from '../support/dialogs.ts' // APP_BASE comes from _helpers.ts, the one place that knows both that the // router is hash-mode and that the URL needs the `/index.php/` prefix (without // it, PHP's built-in server on CI 404s the app directory and every assertion // below runs against a 404 page). This file used to keep a private copy of // that string that was missing the prefix. -import { APP_BASE } from './_helpers' +import { APP_BASE } from './_helpers.ts' // --------------------------------------------------------------------------- // REQ-EP-UI-001: Endpoint Management UI diff --git a/tests/e2e/spec-coverage/features-roadmap.spec.ts b/tests/e2e/spec-coverage/features-roadmap.spec.ts index 6413597db..b3f7271d4 100644 --- a/tests/e2e/spec-coverage/features-roadmap.spec.ts +++ b/tests/e2e/spec-coverage/features-roadmap.spec.ts @@ -6,8 +6,8 @@ * page (manifest type "roadmap"). Reached from the footer nav entry; shows * a "Features" surface with "Show roadmap" / "Suggest a feature" actions. */ -import { test, expect } from '@playwright/test' -import { navTo, trackErrors, assertNoAppErrors, APP_BASE } from './_helpers' +import { expect, test } from '@playwright/test' +import { APP_BASE, assertNoAppErrors, navTo, trackErrors } from './_helpers.ts' test.describe('Features & roadmap β€” index surface', () => { // @e2e openconnector-comprehensive-tests::features-roadmap-page-mounts diff --git a/tests/e2e/spec-coverage/flow-native-sync.spec.ts b/tests/e2e/spec-coverage/flow-native-sync.spec.ts index 722245e06..bf81fea33 100644 --- a/tests/e2e/spec-coverage/flow-native-sync.spec.ts +++ b/tests/e2e/spec-coverage/flow-native-sync.spec.ts @@ -68,14 +68,14 @@ * file genuinely covers rather than inventing anchors that resolve to nothing. */ import type { Browser, Page } from '@playwright/test' -import type { ApiClient } from '../workflows/_fixture' +import type { ApiClient } from '../workflows/_fixture.ts' +import { expect, test } from '@playwright/test' import { execFileSync } from 'child_process' import * as fs from 'fs' import * as path from 'path' -import { expect, test } from '@playwright/test' -import { createObject, deleteObject, makeApiClient } from '../workflows/_fixture' -import { expectRouteMatched, resolveAppRoot } from '../support/appRoot' +import { expectRouteMatched, resolveAppRoot } from '../support/appRoot.ts' +import { createObject, deleteObject, makeApiClient } from '../workflows/_fixture.ts' /** OpenRegister's API root β€” registers, schemas, objects, flows and preflight. */ const OR = '/index.php/apps/openregister/api' @@ -223,7 +223,7 @@ function occ(): OccRunner { // 3. A running container β€” the dev-container layout. const container = process.env.NC_CONTAINER ?? 'nextcloud' - let running = '' + let running try { running = execFileSync( 'docker', @@ -1189,7 +1189,6 @@ test.describe('The decomposed synchronization β€” generated, run, re-run', () => * 4. Resumability after a mid-run suspension * ---------------------------------------------------------------- */ - // eslint-disable-next-line playwright/no-skipped-test test.skip('a run suspended mid-page resumes at the page cursor rather than refetching', async () => { // NOT WRITTEN, and named rather than silently dropped. // diff --git a/tests/e2e/spec-coverage/flow-orchestration.spec.ts b/tests/e2e/spec-coverage/flow-orchestration.spec.ts index 63ff3d733..6ff129681 100644 --- a/tests/e2e/spec-coverage/flow-orchestration.spec.ts +++ b/tests/e2e/spec-coverage/flow-orchestration.spec.ts @@ -42,12 +42,12 @@ * unchanged below. */ import type { Browser, Page } from '@playwright/test' -import type { ApiClient } from '../workflows/_fixture' +import type { ApiClient } from '../workflows/_fixture.ts' import { expect, test } from '@playwright/test' -import { createObject, deleteObject, makeApiClient } from '../workflows/_fixture' -import { APP_BASE } from './_helpers' -import { resolveAppRoot, expectRouteMatched } from '../support/appRoot' +import { expectRouteMatched, resolveAppRoot } from '../support/appRoot.ts' +import { createObject, deleteObject, makeApiClient } from '../workflows/_fixture.ts' +import { APP_BASE } from './_helpers.ts' /** OpenRegister's native flow store β€” a different backend than OR_BASE/OC_API in _fixture.ts, which target openconnector's legacy `flow` schema. */ const FLOWS_API = '/index.php/apps/openregister/api/flows' diff --git a/tests/e2e/spec-coverage/job-management.spec.ts b/tests/e2e/spec-coverage/job-management.spec.ts index 6c134d37c..b9098525c 100644 --- a/tests/e2e/spec-coverage/job-management.spec.ts +++ b/tests/e2e/spec-coverage/job-management.spec.ts @@ -17,14 +17,14 @@ * the main content area rather than relying on table cell content. */ -import { test, expect } from '@playwright/test' -import { appDialog } from '../support/dialogs' +import { expect, test } from '@playwright/test' +import { appDialog } from '../support/dialogs.ts' // APP_BASE comes from _helpers.ts, the one place that knows both that the // router is hash-mode and that the URL needs the `/index.php/` prefix (without // it, PHP's built-in server on CI 404s the app directory and every assertion // below runs against a 404 page). This file used to keep a private copy of // that string that was missing the prefix. -import { APP_BASE } from './_helpers' +import { APP_BASE } from './_helpers.ts' const OR_BASE = '/index.php/apps/openregister/api/objects/integriq' diff --git a/tests/e2e/spec-coverage/mapping-and-search.spec.ts b/tests/e2e/spec-coverage/mapping-and-search.spec.ts index 032d0721c..63c6e98d9 100644 --- a/tests/e2e/spec-coverage/mapping-and-search.spec.ts +++ b/tests/e2e/spec-coverage/mapping-and-search.spec.ts @@ -18,13 +18,13 @@ * navigate directly to a detail URL rather than clicking a table row. */ -import { test, expect } from '@playwright/test' +import { expect, test } from '@playwright/test' // APP_BASE comes from _helpers.ts, the one place that knows both that the // router is hash-mode and that the URL needs the `/index.php/` prefix (without // it, PHP's built-in server on CI 404s the app directory and every assertion // below runs against a 404 page). This file used to keep a private copy of // that string that was missing the prefix. -import { APP_BASE, openAndDismissCreateModal } from './_helpers' +import { APP_BASE, openAndDismissCreateModal } from './_helpers.ts' const OR_BASE = '/index.php/apps/openregister/api/objects/integriq' const API_BASE = '/index.php/apps/integriq/api' diff --git a/tests/e2e/spec-coverage/nav-and-index-pages.spec.ts b/tests/e2e/spec-coverage/nav-and-index-pages.spec.ts index 7f53b28a2..d6dd08af4 100644 --- a/tests/e2e/spec-coverage/nav-and-index-pages.spec.ts +++ b/tests/e2e/spec-coverage/nav-and-index-pages.spec.ts @@ -12,13 +12,13 @@ * and guards against the post-OR-cutover SynchronizationMapper dispatch * regression on the Synchronizations / Endpoints / Cloud events pages. */ -import { test, expect } from '@playwright/test' +import { expect, test } from '@playwright/test' import { - navTo, - trackErrors, assertNoAppErrors, + navTo, openAndDismissCreateModal, -} from './_helpers' + trackErrors, +} from './_helpers.ts' interface IndexPage { navLabel: string diff --git a/tests/e2e/spec-coverage/prometheus-metrics.spec.ts b/tests/e2e/spec-coverage/prometheus-metrics.spec.ts index 3eb078504..21e7aaa02 100644 --- a/tests/e2e/spec-coverage/prometheus-metrics.spec.ts +++ b/tests/e2e/spec-coverage/prometheus-metrics.spec.ts @@ -13,9 +13,9 @@ * numeric values (those change with data in the DB). */ -import { test, expect } from '@playwright/test' +import { expect, test } from '@playwright/test' import * as http from 'http' -import { absoluteUrl } from '../support/baseUrl' +import { absoluteUrl } from '../support/baseUrl.ts' const METRICS_URL = '/index.php/apps/integriq/api/metrics' diff --git a/tests/e2e/spec-coverage/rule-pipeline.spec.ts b/tests/e2e/spec-coverage/rule-pipeline.spec.ts index c006508eb..662805187 100644 --- a/tests/e2e/spec-coverage/rule-pipeline.spec.ts +++ b/tests/e2e/spec-coverage/rule-pipeline.spec.ts @@ -14,14 +14,14 @@ * the deep-link path). Always use the /apps/ prefix. */ -import { test, expect } from '@playwright/test' -import { appDialog } from '../support/dialogs' +import { expect, test } from '@playwright/test' +import { appDialog } from '../support/dialogs.ts' // APP_BASE comes from _helpers.ts, the one place that knows both that the // router is hash-mode and that the URL needs the `/index.php/` prefix (without // it, PHP's built-in server on CI 404s the app directory and every assertion // below runs against a 404 page). This file used to keep a private copy of // that string that was missing the prefix. -import { APP_BASE } from './_helpers' +import { APP_BASE } from './_helpers.ts' // --------------------------------------------------------------------------- // REQ-RULE-UI-001: Rule Management UI diff --git a/tests/e2e/spec-coverage/source-management.spec.ts b/tests/e2e/spec-coverage/source-management.spec.ts index 503b725f5..c608670f1 100644 --- a/tests/e2e/spec-coverage/source-management.spec.ts +++ b/tests/e2e/spec-coverage/source-management.spec.ts @@ -18,14 +18,14 @@ * Use Cards view where the Add Item button is always visible. */ -import { test, expect } from '@playwright/test' -import { appDialog } from '../support/dialogs' +import { expect, test } from '@playwright/test' +import { appDialog } from '../support/dialogs.ts' // APP_BASE comes from _helpers.ts, the one place that knows both that the // router is hash-mode and that the URL needs the `/index.php/` prefix (without // it, PHP's built-in server on CI 404s the app directory and every assertion // below runs against a 404 page). This file used to keep a private copy of // that string that was missing the prefix. -import { APP_BASE } from './_helpers' +import { APP_BASE } from './_helpers.ts' const OR_BASE = '/index.php/apps/openregister/api/objects/integriq' diff --git a/tests/e2e/spec-coverage/sync-editor-bridge-types.spec.ts b/tests/e2e/spec-coverage/sync-editor-bridge-types.spec.ts index 82f4fc3a3..89b86f338 100644 --- a/tests/e2e/spec-coverage/sync-editor-bridge-types.spec.ts +++ b/tests/e2e/spec-coverage/sync-editor-bridge-types.spec.ts @@ -1,3 +1,6 @@ +import type { Page } from '@playwright/test' +import type { ApiClient } from '../workflows/_fixture.ts' + /* * SPDX-FileCopyrightText: 2026 Conduction B.V. * SPDX-License-Identifier: EUPL-1.2 @@ -41,14 +44,9 @@ * must offer it and the target selector must not β€” in the same DOM, at the * same moment. */ -import { test, expect, type Page } from '@playwright/test' -import { APP_BASE, trackErrors, assertNoAppErrors } from './_helpers' -import { - makeApiClient, - createObject, - deleteObject, - type ApiClient, -} from '../workflows/_fixture' +import { expect, test } from '@playwright/test' +import { createObject, deleteObject, makeApiClient } from '../workflows/_fixture.ts' +import { APP_BASE, assertNoAppErrors, trackErrors } from './_helpers.ts' const runId = `sedt-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}` diff --git a/tests/e2e/spec-coverage/synchronization-engine.spec.ts b/tests/e2e/spec-coverage/synchronization-engine.spec.ts index 3d9cf5a52..8699f2357 100644 --- a/tests/e2e/spec-coverage/synchronization-engine.spec.ts +++ b/tests/e2e/spec-coverage/synchronization-engine.spec.ts @@ -17,14 +17,14 @@ * relying on table cell content. */ -import { test, expect } from '@playwright/test' -import { appDialog } from '../support/dialogs' +import { expect, test } from '@playwright/test' +import { appDialog } from '../support/dialogs.ts' // APP_BASE comes from _helpers.ts, the one place that knows both that the // router is hash-mode and that the URL needs the `/index.php/` prefix (without // it, PHP's built-in server on CI 404s the app directory and every assertion // below runs against a 404 page). This file used to keep a private copy of // that string that was missing the prefix. -import { APP_BASE } from './_helpers' +import { APP_BASE } from './_helpers.ts' // --------------------------------------------------------------------------- // REQ-UI-001: Synchronization Management UI diff --git a/tests/e2e/spec-coverage/user-management.spec.ts b/tests/e2e/spec-coverage/user-management.spec.ts index 0bd713c2e..8e4a77823 100644 --- a/tests/e2e/spec-coverage/user-management.spec.ts +++ b/tests/e2e/spec-coverage/user-management.spec.ts @@ -12,9 +12,9 @@ * storageState. */ -import { test, expect } from '@playwright/test' +import { expect, test } from '@playwright/test' import * as http from 'http' -import { BASE_URL, baseUrlParts } from '../support/baseUrl' +import { BASE_URL, baseUrlParts } from '../support/baseUrl.ts' const BASE = BASE_URL const ME_URL = '/index.php/apps/integriq/api/user/me' diff --git a/tests/e2e/spec-coverage/webhooks.spec.ts b/tests/e2e/spec-coverage/webhooks.spec.ts index 906f93068..d69e266db 100644 --- a/tests/e2e/spec-coverage/webhooks.spec.ts +++ b/tests/e2e/spec-coverage/webhooks.spec.ts @@ -15,8 +15,8 @@ * "Add EventSubscription"). The Webhooks index now lists webhook subscriptions β€” * a distinct surface from Consumers β€” and its create button reads "Add Webhook". */ -import { test, expect } from '@playwright/test' -import { navTo, trackErrors, assertNoAppErrors } from './_helpers' +import { expect, test } from '@playwright/test' +import { assertNoAppErrors, navTo, trackErrors } from './_helpers.ts' test.describe('Webhooks β€” index surface', () => { // @e2e openconnector-comprehensive-tests::webhooks-page-mounts diff --git a/tests/e2e/support/appRoot.ts b/tests/e2e/support/appRoot.ts index caec45806..fc9ccbc59 100644 --- a/tests/e2e/support/appRoot.ts +++ b/tests/e2e/support/appRoot.ts @@ -41,7 +41,9 @@ * left to get in the wrong order. */ -import { expect, type Page } from '@playwright/test' +import type { Page } from '@playwright/test' + +import { expect } from '@playwright/test' /** * The app id, and therefore the path `generateUrl` is asked to resolve. diff --git a/tests/e2e/visual/_visual-helpers.ts b/tests/e2e/visual/_visual-helpers.ts index c0b1d3e8b..7e27b6563 100644 --- a/tests/e2e/visual/_visual-helpers.ts +++ b/tests/e2e/visual/_visual-helpers.ts @@ -1,3 +1,6 @@ +import type { Locator, Page } from '@playwright/test' + +import { dismissSupportDialog } from '@conduction/nextcloud-vue/testing/playwright' /* * SPDX-License-Identifier: EUPL-1.2 * @@ -24,8 +27,7 @@ * own baselines on first run, or (b) stay non-gating until baselined in the CI * environment. See tests/e2e/visual/README in-repo wiring notes. */ -import { expect, type Page, type Locator } from '@playwright/test' -import { dismissSupportDialog } from '@conduction/nextcloud-vue/testing/playwright' +import { expect } from '@playwright/test' /** Common screenshot options applied to every visual assertion. */ export const SHOT_OPTIONS = { diff --git a/tests/e2e/visual/integriq.visual.spec.ts b/tests/e2e/visual/integriq.visual.spec.ts index 8e1e527b6..04c5e261a 100644 --- a/tests/e2e/visual/integriq.visual.spec.ts +++ b/tests/e2e/visual/integriq.visual.spec.ts @@ -11,7 +11,7 @@ * See _visual-helpers.ts for the platform-rendering caveat. */ import { test } from '@playwright/test' -import { shootSurface } from './_visual-helpers' +import { shootSurface } from './_visual-helpers.ts' const APP = '/index.php/apps/integriq' diff --git a/tests/e2e/workflows/_fixture.ts b/tests/e2e/workflows/_fixture.ts index 857f6c15b..715ccd735 100644 --- a/tests/e2e/workflows/_fixture.ts +++ b/tests/e2e/workflows/_fixture.ts @@ -1,3 +1,5 @@ +import type { APIRequestContext, Browser } from '@playwright/test' + /* * SPDX-FileCopyrightText: 2026 Conduction B.V. * SPDX-License-Identifier: EUPL-1.2 @@ -40,11 +42,7 @@ * `e2e-` prefix in its name/title so afterAll cleanup can find and * delete exactly the rows this run created and nothing else. */ -import { - type APIRequestContext, - type Browser, - request as pwRequest, -} from '@playwright/test' +import { request as pwRequest } from '@playwright/test' import * as path from 'path' export const OR_BASE = '/index.php/apps/openregister/api/objects/integriq' @@ -72,7 +70,6 @@ async function fetchRequestToken(browser: Browser): Promise { }) // OC.requestToken is the canonical source; fall back to the meta. const token = await page.evaluate(() => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any const oc = (window as any).OC if ( oc @@ -131,9 +128,9 @@ export async function makeApiClient( } /** Unwrap an OR object-create/get response into the bare object record. */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any + function unwrap(body: any): any { - if (body == null) return body + if (body === null || body === undefined) return body // create/update return the object directly or under a key; get returns the object. if (body['@self'] && body.id) return body if (body.object) return body.object @@ -148,9 +145,8 @@ function unwrap(body: any): any { export async function createObject( api: ApiClient, schema: string, - // eslint-disable-next-line @typescript-eslint/no-explicit-any + data: Record, - // eslint-disable-next-line @typescript-eslint/no-explicit-any ): Promise { const resp = await api.request.post(`${OR_BASE}/${schema}`, { data, @@ -165,7 +161,7 @@ export async function createObject( } /** find β€” GET a single object by id/uuid. */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any + export async function find( api: ApiClient, schema: string, @@ -187,7 +183,6 @@ export async function findAll( api: ApiClient, schema: string, query: Record = {}, - // eslint-disable-next-line @typescript-eslint/no-explicit-any ): Promise { const qs = new URLSearchParams({ _limit: '200', @@ -214,9 +209,8 @@ export async function updateObject( api: ApiClient, schema: string, id: string, - // eslint-disable-next-line @typescript-eslint/no-explicit-any + data: Record, - // eslint-disable-next-line @typescript-eslint/no-explicit-any ): Promise { const resp = await api.request.put(`${OR_BASE}/${schema}/${id}`, { data, @@ -241,7 +235,7 @@ export async function deleteObject( }) if (!resp.ok() && resp.status() !== 404) { // Cleanup must be best-effort; log but don't throw so afterAll keeps going. - // eslint-disable-next-line no-console + console.warn( `deleteObject(${schema}/${id}) returned ${resp.status()}: ${await resp.text()}`, ) @@ -257,7 +251,7 @@ export async function cleanupByPrefix( schema: string, prefix: string, ): Promise { - let rows: unknown[] = [] + let rows: unknown[] try { rows = await findAll(api, schema, { _search: prefix }) } catch { @@ -276,7 +270,7 @@ export async function cleanupByPrefix( } /** Extract the stable id (uuid or id) from a persisted OR record. */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any + export function idOf(obj: any): string { return String(obj?.id ?? obj?.uuid ?? obj?.['@self']?.id ?? '') } diff --git a/tests/e2e/workflows/source-mapping-crud.spec.ts b/tests/e2e/workflows/source-mapping-crud.spec.ts index dee9590c9..446b061d3 100644 --- a/tests/e2e/workflows/source-mapping-crud.spec.ts +++ b/tests/e2e/workflows/source-mapping-crud.spec.ts @@ -1,3 +1,6 @@ +import type { Page } from '@playwright/test' +import type { ApiClient } from './_fixture.ts' + /* * SPDX-FileCopyrightText: 2026 Conduction B.V. * SPDX-License-Identifier: EUPL-1.2 @@ -43,18 +46,16 @@ * sort, so these specs no longer encode an assumption about how many rows the * rest of the suite happens to leave behind. */ -import { test, expect, type Page } from '@playwright/test' -import { appDialog } from '../support/dialogs' +import { expect, test } from '@playwright/test' +import { appDialog } from '../support/dialogs.ts' import { - makeApiClient, - makeRunId, + cleanupByPrefix, find, findAll, - deleteObject, - cleanupByPrefix, idOf, - type ApiClient, -} from './_fixture' + makeApiClient, + makeRunId, +} from './_fixture.ts' let api: ApiClient const RUN = makeRunId() diff --git a/tests/e2e/workflows/synchronization-workflow.spec.ts b/tests/e2e/workflows/synchronization-workflow.spec.ts index 4f431fb32..0623c9529 100644 --- a/tests/e2e/workflows/synchronization-workflow.spec.ts +++ b/tests/e2e/workflows/synchronization-workflow.spec.ts @@ -1,3 +1,5 @@ +import type { ApiClient } from './_fixture.ts' + /* * SPDX-FileCopyrightText: 2026 Conduction B.V. * SPDX-License-Identifier: EUPL-1.2 @@ -42,19 +44,16 @@ * fixme bodies are written to PASS the moment the bugs are fixed, so they * double as regression guards. */ -import { test, expect } from '@playwright/test' +import { expect, test } from '@playwright/test' import { - makeApiClient, - makeRunId, + cleanupByPrefix, createObject, - idOf, findAll, - deleteObject, - cleanupByPrefix, - OR_BASE, + idOf, + makeApiClient, + makeRunId, OC_API, - type ApiClient, -} from './_fixture' +} from './_fixture.ts' const RUN = makeRunId() diff --git a/tests/l10n/check-l10n-parity.js b/tests/l10n/check-l10n-parity.js index 70e99a9a9..460ccf467 100644 --- a/tests/l10n/check-l10n-parity.js +++ b/tests/l10n/check-l10n-parity.js @@ -89,7 +89,7 @@ function loadJsonSet (file) { /** True when a translation value is empty (string) or has an empty plural. */ function isEmpty (v) { - if (v == null) { + if ((v === null || v === undefined)) { return true } if (Array.isArray(v)) { @@ -135,8 +135,8 @@ for (const set of sets) { failures.push({ set: set.kind, loc, kind: 'UNPARSEABLE', detail: e.message }) continue } - const missing = enKeys.filter((k) => !Object.prototype.hasOwnProperty.call(locObj, k)) - const empty = enKeys.filter((k) => Object.prototype.hasOwnProperty.call(locObj, k) && isEmpty(locObj[k])) + const missing = enKeys.filter((k) => !Object.hasOwn(locObj, k)) + const empty = enKeys.filter((k) => Object.hasOwn(locObj, k) && isEmpty(locObj[k])) if (missing.length || empty.length) { failures.push({ set: set.kind, loc, kind: 'INCOMPLETE', missing, empty, total: enKeys.length }) } diff --git a/tests/l10n/check-l10n.js b/tests/l10n/check-l10n.js index 8d1492933..4fb347ed4 100644 --- a/tests/l10n/check-l10n.js +++ b/tests/l10n/check-l10n.js @@ -1,7 +1,5 @@ #!/usr/bin/env node -/* eslint-disable n/no-process-exit */ -/* eslint-disable no-console */ -/* eslint-disable n/shebang */ + /** * l10n extraction / drift check β€” FRONTEND catalogue. * @@ -69,7 +67,6 @@ const fs = require('fs') const path = require('path') - const { loadJsTranslations, serializeJs, @@ -132,7 +129,7 @@ for (const file of files) { const missing = [] for (const [key, locations] of used) { - if (!Object.prototype.hasOwnProperty.call(translations, key)) { + if (!Object.hasOwn(translations, key)) { missing.push({ key, locations: [...locations] }) } } diff --git a/tests/validate-json-strict.js b/tests/validate-json-strict.js index 8a27cecf1..cd1982f64 100644 --- a/tests/validate-json-strict.js +++ b/tests/validate-json-strict.js @@ -45,7 +45,6 @@ function targetFiles() { // `pathPrefix` is the JSON-pointer-ish path used in the error message. function parseStrict(text, label) { const dupErrors = [] - const reviverPathStack = [] // JSON.parse's reviver can't see duplicates (the object is already // collapsed). So we re-implement just enough: tokenise object keys. // Simpler robust approach: walk the raw text with a tiny tokenizer. @@ -101,7 +100,7 @@ function parseStrict(text, label) { return } let idx = 0 - // eslint-disable-next-line no-constant-condition + while (true) { readValue(`${pathStr}/${idx}`) idx++ @@ -126,7 +125,7 @@ function parseStrict(text, label) { i++ return } - // eslint-disable-next-line no-constant-condition + while (true) { skipWs() if (text[i] !== '"') err('expected string key in object') diff --git a/tests/validate-manifest.js b/tests/validate-manifest.js index f7c484274..4c83c2c8e 100644 --- a/tests/validate-manifest.js +++ b/tests/validate-manifest.js @@ -91,8 +91,8 @@ function loadAjv() { // The canonical schema uses JSON Schema draft 2020-12. Standard Ajv (v7+) // does not auto-load the 2020 meta-schema; we need the `ajv/dist/2020` // entry point. - let Ajv2020 = null - let addFormats = null + let Ajv2020 + let addFormats try { // Ajv 8+ ships the 2020 draft entry point. Ajv2020 = require('ajv/dist/2020').default || require('ajv/dist/2020') diff --git a/tests/vitest/actionFormsShared.spec.js b/tests/vitest/actionFormsShared.spec.js index a873d166b..33a65b5a2 100644 --- a/tests/vitest/actionFormsShared.spec.js +++ b/tests/vitest/actionFormsShared.spec.js @@ -12,14 +12,14 @@ * @nextcloud/axios is mocked; @nextcloud/router is the stub from the config. */ -import { describe, it, expect, vi, beforeEach } from 'vitest' +import { beforeEach, describe, expect, it, vi } from 'vitest' const get = vi.fn() vi.mock('@nextcloud/axios', () => ({ default: { get: (...a) => get(...a) } })) import { - patchMethod, fetchOpenRegisterCollection, + patchMethod, valueProp, } from '../../src/views/Rule/actionForms/shared.js' diff --git a/tests/vitest/buildAuthenticationConfiguration.spec.js b/tests/vitest/buildAuthenticationConfiguration.spec.js index 7c6667a9f..a29d4ee53 100644 --- a/tests/vitest/buildAuthenticationConfiguration.spec.js +++ b/tests/vitest/buildAuthenticationConfiguration.spec.js @@ -13,7 +13,7 @@ * harness is node-env and mounts no .vue, so the logic lives in this pure helper by design). */ -import { describe, it, expect } from 'vitest' +import { describe, expect, it } from 'vitest' import { buildAuthenticationConfiguration } from '../../src/modals/Rule/buildAuthenticationConfiguration.js' describe('buildAuthenticationConfiguration', () => { @@ -26,7 +26,7 @@ describe('buildAuthenticationConfiguration', () => { apiKeys: [{ apiKey: '', user: [] }], }) - expect(Object.prototype.hasOwnProperty.call(auth, 'keys')).toBe(false) + expect(Object.hasOwn(auth, 'keys')).toBe(false) expect(auth).toEqual({ type: 'api-key', users: [], groups: [] }) }) @@ -36,7 +36,7 @@ describe('buildAuthenticationConfiguration', () => { users: [], groups: [], }) - expect(Object.prototype.hasOwnProperty.call(auth, 'keys')).toBe(false) + expect(Object.hasOwn(auth, 'keys')).toBe(false) }) it('OMITS keys when rows are incomplete (apiKey without a selected user, or user without a key)', () => { @@ -49,7 +49,7 @@ describe('buildAuthenticationConfiguration', () => { { apiKey: '', user: { id: 'alice' } }, ], }) - expect(Object.prototype.hasOwnProperty.call(auth, 'keys')).toBe(false) + expect(Object.hasOwn(auth, 'keys')).toBe(false) }) it('EMITS keys (as apiKey => userId maps) only for complete new rows', () => { diff --git a/tests/vitest/catalogStore.spec.js b/tests/vitest/catalogStore.spec.js index b7e0c7319..b77afdf18 100644 --- a/tests/vitest/catalogStore.spec.js +++ b/tests/vitest/catalogStore.spec.js @@ -16,8 +16,8 @@ * @spec openspec/specs/connector-catalog/spec.md#requirement-catalog-lists-adapters-seeded-source-templates-and-configuration-templates-with-category-filter-and-status-badges-req-001 */ -import { describe, it, expect, vi, beforeEach } from 'vitest' import { createPinia, setActivePinia } from 'pinia' +import { beforeEach, describe, expect, it, vi } from 'vitest' const get = vi.fn() const post = vi.fn() diff --git a/tests/vitest/consumerDraft.spec.js b/tests/vitest/consumerDraft.spec.js index 14091e219..939a2f737 100644 --- a/tests/vitest/consumerDraft.spec.js +++ b/tests/vitest/consumerDraft.spec.js @@ -26,19 +26,19 @@ import fs from 'node:fs' import path from 'node:path' import { fileURLToPath } from 'node:url' -import { describe, it, expect } from 'vitest' +import { describe, expect, it } from 'vitest' import { AUTHORIZATION_TYPES, - CREDENTIALLESS_AUTHORIZATION_TYPES, - QUOTA_PERIODS, buildConsumerPayload, buildQuota, buildRateLimit, carriesCredential, consumerDraftFromItem, + CREDENTIALLESS_AUTHORIZATION_TYPES, emptyConsumerDraft, normaliseList, positiveIntOrNull, + QUOTA_PERIODS, } from '../../src/modals/v2/consumerDraft.js' const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..') diff --git a/tests/vitest/editorModalSlotContract.spec.js b/tests/vitest/editorModalSlotContract.spec.js index 18515ec78..3ac50ebbd 100644 --- a/tests/vitest/editorModalSlotContract.spec.js +++ b/tests/vitest/editorModalSlotContract.spec.js @@ -30,7 +30,7 @@ import fs from 'fs' import path from 'path' import { fileURLToPath } from 'url' -import { describe, it, expect } from 'vitest' +import { describe, expect, it } from 'vitest' const here = path.dirname(fileURLToPath(import.meta.url)) @@ -88,7 +88,6 @@ function loadSfcOptions(relPath) { .join('\n') const body = `${stubs}\n${script.replace('export default', 'return')}` - // eslint-disable-next-line no-new-func return new Function(body)() } diff --git a/tests/vitest/formsBridge.spec.js b/tests/vitest/formsBridge.spec.js index 0069acd26..3c65a8cb0 100644 --- a/tests/vitest/formsBridge.spec.js +++ b/tests/vitest/formsBridge.spec.js @@ -13,16 +13,16 @@ * @spec openspec/changes/nextcloud-forms-connector/specs/sync-editor-ui/spec.md#requirement-field-mapping-helper-prefilled-from-form-questions-req-syncui-009 */ -import { describe, it, expect } from 'vitest' +import { describe, expect, it } from 'vitest' import { - NEXTCLOUD_FORM_KIND, - MULTI_VALUE_QUESTION_TYPES, + ambiguousQuestionTexts, extractResults, + isArrayValuedQuestion, mapFormOptions, - normaliseQuestion, mapQuestionDescriptors, - isArrayValuedQuestion, - ambiguousQuestionTexts, + MULTI_VALUE_QUESTION_TYPES, + NEXTCLOUD_FORM_KIND, + normaliseQuestion, } from '../../src/views/Synchronization/formsBridge.js' describe('kind discriminator', () => { diff --git a/tests/vitest/jobDraft.spec.js b/tests/vitest/jobDraft.spec.js index 4d692e931..6db03752a 100644 --- a/tests/vitest/jobDraft.spec.js +++ b/tests/vitest/jobDraft.spec.js @@ -23,14 +23,14 @@ import fs from 'node:fs' import path from 'node:path' import { fileURLToPath } from 'node:url' -import { describe, it, expect } from 'vitest' +import { describe, expect, it } from 'vitest' import { - SYNCHRONIZATION_ACTION_CLASS, coerceNumber, dateValueFromStored, formatDateValue, groupFieldRuns, readSynchronizationId, + SYNCHRONIZATION_ACTION_CLASS, writeSynchronizationId, } from '../../src/modals/v2/jobDraft.js' diff --git a/tests/vitest/liveObjectSubscriptionCleanup.spec.js b/tests/vitest/liveObjectSubscriptionCleanup.spec.js index bafa7dee4..bd2e28678 100644 --- a/tests/vitest/liveObjectSubscriptionCleanup.spec.js +++ b/tests/vitest/liveObjectSubscriptionCleanup.spec.js @@ -1,3 +1,4 @@ +import { mount } from '@vue/test-utils' /** * @vitest-environment jsdom * @@ -21,8 +22,7 @@ * * @spec openspec/specs/realtime-updates/spec.md */ -import { describe, it, expect, vi, beforeEach } from 'vitest' -import { mount } from '@vue/test-utils' +import { beforeEach, describe, expect, it, vi } from 'vitest' import { defineComponent, h } from 'vue' const unsubscribe = vi.fn() diff --git a/tests/vitest/ncButtonSubmitType.spec.js b/tests/vitest/ncButtonSubmitType.spec.js index c734177b4..442b667fa 100644 --- a/tests/vitest/ncButtonSubmitType.spec.js +++ b/tests/vitest/ncButtonSubmitType.spec.js @@ -25,9 +25,9 @@ * the negative controls fail loudly rather than silently passing. */ -import { describe, it, expect } from 'vitest' -import { defineComponent, h } from 'vue' import { mount } from '@vue/test-utils' +import { describe, expect, it } from 'vitest' +import { defineComponent, h } from 'vue' import NcButton from '@nextcloud/vue/components/NcButton' /** diff --git a/tests/vitest/routerRef.spec.js b/tests/vitest/routerRef.spec.js index 9bcdde5b6..ea33fc636 100644 --- a/tests/vitest/routerRef.spec.js +++ b/tests/vitest/routerRef.spec.js @@ -7,8 +7,8 @@ * getRouter() (they run without Vue component context, so no this.$router). */ -import { describe, it, expect, beforeEach } from 'vitest' -import { setRouter, getRouter } from '../../src/handlers/routerRef.js' +import { beforeEach, describe, expect, it } from 'vitest' +import { getRouter, setRouter } from '../../src/handlers/routerRef.js' describe('routerRef', () => { beforeEach(() => { diff --git a/tests/vitest/ruleDraft.spec.js b/tests/vitest/ruleDraft.spec.js index 1710ed52a..60115bdce 100644 --- a/tests/vitest/ruleDraft.spec.js +++ b/tests/vitest/ruleDraft.spec.js @@ -23,17 +23,17 @@ * unknown type. A typo here is a rule that cannot run. */ -import { describe, it, expect } from 'vitest' +import { describe, expect, it } from 'vitest' import { ACTION_OPTIONS, ACTION_TYPES, DEFAULT_ERROR_CONFIG, EMPTY_ROOT_GROUP, - TIMING_OPTIONS, - UNDISPATCHED_ACTION_TYPES, emptyRuleDraft, normaliseConditions, serializeRuleConditions, + TIMING_OPTIONS, + UNDISPATCHED_ACTION_TYPES, } from '../../src/views/Rule/ruleDraft.js' describe('normaliseConditions', () => { diff --git a/tests/vitest/runTargets.spec.js b/tests/vitest/runTargets.spec.js index cf090527d..d2e759b12 100644 --- a/tests/vitest/runTargets.spec.js +++ b/tests/vitest/runTargets.spec.js @@ -19,12 +19,12 @@ * @nextcloud/l10n is aliased to a deterministic stub in vitest.config.js. */ -import { describe, it, expect } from 'vitest' +import { describe, expect, it } from 'vitest' import { + countUuids, getRunDescriptor, initialOptionValues, visibleOptions, - countUuids, } from '../../src/modals/v2/runTargets.js' describe('getRunDescriptor', () => { diff --git a/tests/vitest/sourceCredentialRef.spec.js b/tests/vitest/sourceCredentialRef.spec.js index 1e62227f2..f4a658ad6 100644 --- a/tests/vitest/sourceCredentialRef.spec.js +++ b/tests/vitest/sourceCredentialRef.spec.js @@ -15,17 +15,17 @@ * β€’ the OR list-envelope unwrap + NcSelect option mapping (soft-fail-safe). */ -import { describe, it, expect } from 'vitest' +import { describe, expect, it } from 'vitest' import { - EMBEDDED_SECRET_FIELDS, CALLING_APP_ID, - readCredentialRef, - isBrokered, - readCredentialId, - writeCredentialRef, clearCredentialRef, + EMBEDDED_SECRET_FIELDS, extractCredentialResults, + isBrokered, mapCredentialOptions, + readCredentialId, + readCredentialRef, + writeCredentialRef, } from '../../src/modals/v2/sourceCredentialRef.js' const UUID = '00000000-0000-0000-0000-000000000000' diff --git a/tests/vitest/tablesBridge.spec.js b/tests/vitest/tablesBridge.spec.js index 71d168a88..097df0a7a 100644 --- a/tests/vitest/tablesBridge.spec.js +++ b/tests/vitest/tablesBridge.spec.js @@ -12,17 +12,17 @@ * @spec openspec/changes/tables-bridge/specs/sync-editor-ui/spec.md#requirement-column-mapping-helper-prefilled-from-table-schema-req-syncui-007 */ -import { describe, it, expect } from 'vitest' +import { describe, expect, it } from 'vitest' import { - NEXTCLOUD_TABLE_KIND, + columnTypeHint, extractResults, + mapColumnDescriptors, + mappedValueFor, mapTableOptions, + NEXTCLOUD_TABLE_KIND, normaliseColumn, - mapColumnDescriptors, - columnTypeHint, readColumnMapping, upsertColumnMapping, - mappedValueFor, } from '../../src/views/Synchronization/tablesBridge.js' describe('kind discriminator', () => {