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', () => {