From 3bd0ec3d7797df6e76327045c7854a7409b5e4ed Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Tue, 1 Sep 2026 11:44:45 +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. One finding here was a real defect rather than tidiness. `no-dupe-keys` flagged this assertion: expect(moderationItemTitle({ name: ' ', name: 'Real' })).toBe('Real') The second `name` silently overwrites the first, so the object actually built was `{ name: 'Real' }` and the test named "ignores blank/whitespace title fields" never took the blank branch at all. It could not have failed for the reason it claimed. It now passes `{ name: ' ', title: 'Real' }`, which does exercise the fall-through. `TITLE_FIELDS` carried the same slip, listing `'name'` twice, so the second entry was dead. Removed. The suite is 5 passed. --- eslint.config.mjs | 142 +++++++++++++++++- package.json | 2 +- scripts/build-l10n-js.js | 3 + scripts/check-schema-l10n.js | 3 + scripts/check-vue-demi.js | 2 +- src/utils/moderationItem.js | 9 +- tests/e2e/global-setup.ts | 10 +- tests/e2e/manifest-pages.spec.ts | 6 +- tests/e2e/org-archimate-export.spec.ts | 11 +- tests/e2e/playwright.config.ts | 3 +- tests/e2e/sbom-import.spec.ts | 16 +- tests/e2e/smoke/app-mounts.spec.ts | 2 +- tests/e2e/spec-coverage/_helpers.ts | 6 +- .../e2e/spec-coverage/catalog-ratings.spec.ts | 32 ++-- .../spec-coverage/compliance-matrix.spec.ts | 6 +- .../contract-administration.spec.ts | 4 +- .../contract-approval-panel.spec.ts | 4 +- tests/e2e/spec-coverage/dashboard.spec.ts | 10 +- .../demo-data-setup-step.spec.ts | 8 +- .../spec-coverage/features-roadmap.spec.ts | 10 +- .../gemma-faceted-search.spec.ts | 25 +-- tests/e2e/spec-coverage/index-pages.spec.ts | 12 +- .../e2e/spec-coverage/license-posture.spec.ts | 6 +- .../spec-coverage/lifecycle-roadmap.spec.ts | 6 +- tests/e2e/spec-coverage/page-surfaces.spec.ts | 6 +- tests/e2e/spec-coverage/settings.spec.ts | 6 +- tests/e2e/spec-coverage/suite-wizard.spec.ts | 24 +-- .../vulnerability-tracking.spec.ts | 6 +- tests/e2e/visual/_visual-helpers.ts | 4 +- tests/e2e/visual/stackiq.visual.spec.ts | 6 +- tests/e2e/workflows/_fixtures.ts | 11 +- tests/e2e/workflows/_ui.ts | 14 +- tests/e2e/workflows/crud-persistence.spec.ts | 30 ++-- .../e2e/workflows/org-export-workflow.spec.ts | 12 +- tests/e2e/workflows/organisatie-crud.spec.ts | 28 ++-- tests/l10n/check-l10n-parity.js | 6 +- tests/l10n/check-l10n.js | 4 +- tests/validate-manifest.js | 4 +- tests/vitest/adminApi.spec.js | 4 +- tests/vitest/complianceMatrix.spec.js | 10 +- tests/vitest/contractCost.spec.js | 8 +- tests/vitest/facetSchema.spec.js | 4 +- tests/vitest/heartbeat.spec.js | 2 +- tests/vitest/licensePosture.spec.js | 20 +-- tests/vitest/lifecyclePhase.spec.js | 6 +- tests/vitest/manifestWidgetIcons.spec.js | 2 +- tests/vitest/moderationItem.spec.js | 10 +- tests/vitest/navigationStore.spec.js | 2 +- tests/vitest/openDataProjection.spec.js | 6 +- tests/vitest/orClient.spec.js | 10 +- tests/vitest/portfolioReport.spec.js | 8 +- tests/vitest/reviewAggregate.spec.js | 2 +- tests/vitest/reviewForm.spec.js | 6 +- tests/vitest/sbomVulnerabilityMatch.spec.js | 6 +- tests/vitest/sectionInfoSlot.spec.js | 3 +- tests/vitest/settingsInfoPanels.spec.js | 10 +- tests/vitest/suiteWizard.spec.js | 6 +- tests/vitest/translationBadge.spec.js | 4 +- tests/vitest/vulnerabilityExposure.spec.js | 6 +- tests/vitest/vulnerabilitySeverity.spec.js | 8 +- 60 files changed, 391 insertions(+), 251 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 9bad6e2d..c7c8ac1e 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -144,11 +144,32 @@ 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', + }, + }, + + { + // `_` / `__` 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 +186,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'], + }, + + { // Test globals. Several apps keep their spec files INSIDE `src/`, which the // lint script scans, and neither `@nextcloud/eslint-config` nor the runner diff --git a/package.json b/package.json index 8d700702..dc0a8fcb 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,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 && npm run format:fix", "test": "jest --silent", "test-coverage": "jest --silent --coverage", diff --git a/scripts/build-l10n-js.js b/scripts/build-l10n-js.js index 176ef519..335b08d2 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-schema-l10n.js b/scripts/check-schema-l10n.js index 8a860b33..3c4b2626 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/check-vue-demi.js b/scripts/check-vue-demi.js index 34da9a90..7b5f1842 100644 --- a/scripts/check-vue-demi.js +++ b/scripts/check-vue-demi.js @@ -24,9 +24,9 @@ * then hard-fail. Never silently continue. */ +const { spawnSync } = require('child_process') const fs = require('fs') const path = require('path') -const { spawnSync } = require('child_process') const SHIM = path.resolve( __dirname, diff --git a/src/utils/moderationItem.js b/src/utils/moderationItem.js index f54a260d..07fb0cb8 100644 --- a/src/utils/moderationItem.js +++ b/src/utils/moderationItem.js @@ -13,14 +13,7 @@ * SPDX-License-Identifier: EUPL-1.2 */ -const TITLE_FIELDS = [ - 'name', - 'name', - 'titel', - 'title', - 'organization', - 'organisation', -] +const TITLE_FIELDS = ['name', 'titel', 'title', 'organization', 'organisation'] const SUBTITLE_FIELDS = [ 'email', 'contactEmail', diff --git a/tests/e2e/global-setup.ts b/tests/e2e/global-setup.ts index b6d5f3b2..2a8dc4b7 100644 --- a/tests/e2e/global-setup.ts +++ b/tests/e2e/global-setup.ts @@ -8,10 +8,12 @@ * in the static HTML. We must wait for the input fields to hydrate before filling. */ -import { chromium, type FullConfig } from '@playwright/test' -import * as path from 'path' +import type { FullConfig } from '@playwright/test' + +import { chromium } from '@playwright/test' import * as fs from 'fs' -import { resolveBaseUrl } from './base-url' +import * as path from 'path' +import { resolveBaseUrl } from './base-url.ts' const AUTH_DIR = path.resolve(__dirname, '.auth') const STORAGE_STATE = path.join(AUTH_DIR, 'admin.json') @@ -91,7 +93,7 @@ export default async function globalSetup(config: FullConfig): Promise { await page.evaluate(() => { try { window.localStorage.setItem('cn-walkthrough-seen:stackiq', '999.0.0') - } catch (e) { + } catch { // localStorage unavailable — specs fall back to dismissing by hand. } }) diff --git a/tests/e2e/manifest-pages.spec.ts b/tests/e2e/manifest-pages.spec.ts index 6ea7a166..7526cc33 100644 --- a/tests/e2e/manifest-pages.spec.ts +++ b/tests/e2e/manifest-pages.spec.ts @@ -33,8 +33,10 @@ * and carry standalone `@e2e exclude` directives in their spec blocks. */ -import { test, expect, type Page } from '@playwright/test' -import { APP_PATH } from './base-url' +import type { Page } from '@playwright/test' + +import { expect, test } from '@playwright/test' +import { APP_PATH } from './base-url.ts' // Was the hardcoded pretty path `/apps/stackiq`. See the APP_PATH // docblock in tests/e2e/base-url.ts: without a rewrite rule that path is not a diff --git a/tests/e2e/org-archimate-export.spec.ts b/tests/e2e/org-archimate-export.spec.ts index 13a66029..2b92dede 100644 --- a/tests/e2e/org-archimate-export.spec.ts +++ b/tests/e2e/org-archimate-export.spec.ts @@ -75,13 +75,10 @@ * @e2e org-archimate-export::boolean-parameters-accept-various-truthy-values */ -import { - test, - expect, - request as playwrightRequest, - type Page, -} from '@playwright/test' -import { APP_PATH, resolveBaseUrl } from './base-url' +import type { Page } from '@playwright/test' + +import { expect, request as playwrightRequest, test } from '@playwright/test' +import { APP_PATH, resolveBaseUrl } from './base-url.ts' // --------------------------------------------------------------------------- // Fixture setup diff --git a/tests/e2e/playwright.config.ts b/tests/e2e/playwright.config.ts index cc05c8cb..7d3958a8 100644 --- a/tests/e2e/playwright.config.ts +++ b/tests/e2e/playwright.config.ts @@ -57,8 +57,7 @@ import { defineConfig, devices } from '@playwright/test' import * as path from 'path' - -import { BASE_URL } from './base-url' +import { BASE_URL } from './base-url.ts' const APP_ROOT = path.resolve(__dirname, '..', '..') diff --git a/tests/e2e/sbom-import.spec.ts b/tests/e2e/sbom-import.spec.ts index ebf912d8..66544a80 100644 --- a/tests/e2e/sbom-import.spec.ts +++ b/tests/e2e/sbom-import.spec.ts @@ -42,17 +42,19 @@ * `cyclonedx-1.5-valid.json`) is uploaded through the real file input. */ -import { test, expect, type Page } from '@playwright/test' +import type { Page } from '@playwright/test' + +import { expect, test } from '@playwright/test' import * as path from 'path' +import { APP_PATH } from './base-url.ts' +import { dismissWalkthrough } from './spec-coverage/_helpers.ts' import { + cleanupByToken, + createObject, newApiContext, resolveConfig, - createObject, - cleanupByToken, RUN_ID, -} from './workflows/_fixtures' -import { dismissWalkthrough } from './spec-coverage/_helpers' -import { APP_PATH } from './base-url' +} from './workflows/_fixtures.ts' const FIXTURES_DIR = path.resolve(__dirname, '../fixtures/sbom') const CYCLONEDX_16 = path.join(FIXTURES_DIR, 'cyclonedx-1.6-valid.json') // 3 components @@ -109,7 +111,7 @@ async function openComponentsTab(page: Page): Promise { // tests/e2e/base-url.ts. `domcontentloaded`, not `networkidle`: the SPA keeps // a background poll alive so the network never goes idle; the app-root wait // below is the real readiness signal. - await page.goto(`${APP_PATH}/#/moduleversies/${moduleVersieId}`, { + await page.goto(`${APP_PATH}/moduleversies/${moduleVersieId}`, { waitUntil: 'domcontentloaded', }) await page diff --git a/tests/e2e/smoke/app-mounts.spec.ts b/tests/e2e/smoke/app-mounts.spec.ts index 687e11b0..673bb688 100644 --- a/tests/e2e/smoke/app-mounts.spec.ts +++ b/tests/e2e/smoke/app-mounts.spec.ts @@ -31,7 +31,7 @@ * Run: npx playwright test --project smoke */ -import { test, expect } from '@playwright/test' +import { expect, test } from '@playwright/test' const ROUTES = [ { name: 'app root', path: '/index.php/apps/stackiq/' }, diff --git a/tests/e2e/spec-coverage/_helpers.ts b/tests/e2e/spec-coverage/_helpers.ts index 6487256c..97210bfe 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-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2026 Conduction B.V. /** @@ -10,8 +12,8 @@ * buttons, view toggles, empty-state, dashboard widgets, settings sections) — * no Vue `$data`/`__vue__` patching. */ -import { expect, type Page } from '@playwright/test' -import { APP_PATH } from '../base-url' +import { expect } from '@playwright/test' +import { APP_PATH } from '../base-url.ts' // Was the hardcoded pretty path `/apps/stackiq`, which only resolves // behind a rewrite rule. See the APP_PATH docblock in tests/e2e/base-url.ts — diff --git a/tests/e2e/spec-coverage/catalog-ratings.spec.ts b/tests/e2e/spec-coverage/catalog-ratings.spec.ts index 4aca5eb9..13634eac 100644 --- a/tests/e2e/spec-coverage/catalog-ratings.spec.ts +++ b/tests/e2e/spec-coverage/catalog-ratings.spec.ts @@ -1,3 +1,7 @@ +import type { APIRequestContext } from '@playwright/test' +import type { Page } from '@playwright/test' +import type { VoorzieningenConfig } from '../workflows/_fixtures.ts' + // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2026 Conduction B.V. /** @@ -28,13 +32,16 @@ * * @spec openspec/specs/catalog-ratings/spec.md */ +import { expect, request as playwrightRequest, test } from '@playwright/test' import { - test, - expect, - request as playwrightRequest, - type Page, -} from '@playwright/test' -import type { APIRequestContext } from '@playwright/test' + BASE_URL, + createObject, + deleteObject, + findAll, + newApiContext, + resolveConfig, + RUN_ID, +} from '../workflows/_fixtures.ts' import { APP_BASE, APP_MAIN, @@ -43,19 +50,8 @@ import { dismissSupportDialog, dismissWalkthrough, expectNoAppErrors, - gotoAppRoute, navClickTo, -} from './_helpers' -import { - BASE_URL, - RUN_ID, - createObject, - findAll, - newApiContext, - resolveConfig, - deleteObject, - type VoorzieningenConfig, -} from '../workflows/_fixtures' +} from './_helpers.ts' const MODULE_NAME = `Review subject ${RUN_ID}` /** Unique per test so the moderation queue row this test acts on is its own. */ diff --git a/tests/e2e/spec-coverage/compliance-matrix.spec.ts b/tests/e2e/spec-coverage/compliance-matrix.spec.ts index aef90820..720d3450 100644 --- a/tests/e2e/spec-coverage/compliance-matrix.spec.ts +++ b/tests/e2e/spec-coverage/compliance-matrix.spec.ts @@ -14,14 +14,14 @@ * @spec openspec/specs/module-compliance-assessment/spec.md * @spec openspec/specs/bio-compliance-assessment/spec.md */ -import { test, expect } from '@playwright/test' +import { expect, test } from '@playwright/test' import { APP_MAIN, collectAppErrors, expectNoAppErrors, navClickTo, -} from './_helpers' -import { ComplianceMatrixView } from './page-components' +} from './_helpers.ts' +import { ComplianceMatrixView } from './page-components.ts' // @e2e module-compliance-assessment::matrix-renders-the-three-cell-states // @e2e module-compliance-assessment::matrix-selection-is-shareable diff --git a/tests/e2e/spec-coverage/contract-administration.spec.ts b/tests/e2e/spec-coverage/contract-administration.spec.ts index 7ce3afac..63f14359 100644 --- a/tests/e2e/spec-coverage/contract-administration.spec.ts +++ b/tests/e2e/spec-coverage/contract-administration.spec.ts @@ -13,13 +13,13 @@ * * @spec openspec/specs/contract-administration/spec.md */ -import { test, expect } from '@playwright/test' +import { expect, test } from '@playwright/test' import { APP_MAIN, collectAppErrors, expectNoAppErrors, navClickTo, -} from './_helpers' +} from './_helpers.ts' // @e2e contract-administration::index-columns-render-real-data // @e2e contract-administration::expiring-soon-filter-shows-only-contracts-in-the-window diff --git a/tests/e2e/spec-coverage/contract-approval-panel.spec.ts b/tests/e2e/spec-coverage/contract-approval-panel.spec.ts index 92ceedd0..12d54473 100644 --- a/tests/e2e/spec-coverage/contract-approval-panel.spec.ts +++ b/tests/e2e/spec-coverage/contract-approval-panel.spec.ts @@ -14,14 +14,14 @@ * * @spec openspec/changes/stackiq-contracts-to-decidesk/specs/contract-decision-delegation/spec.md */ -import { test, expect } from '@playwright/test' +import { expect, test } from '@playwright/test' import { APP_MAIN, collectAppErrors, expectNoAppErrors, gotoAppRoute, navClickTo, -} from './_helpers' +} from './_helpers.ts' // @e2e contract-decision-delegation::approval-panel-shows-projected-state-and-submit-action // @e2e contract-decision-delegation::approval-action-hidden-when-delegation-is-not-configured diff --git a/tests/e2e/spec-coverage/dashboard.spec.ts b/tests/e2e/spec-coverage/dashboard.spec.ts index a765c0b2..d6418c6d 100644 --- a/tests/e2e/spec-coverage/dashboard.spec.ts +++ b/tests/e2e/spec-coverage/dashboard.spec.ts @@ -10,14 +10,14 @@ * "Ga naar Organisaties" navigation button which routes to the organisaties * index (navigationStore.setSelected('organisaties')). */ -import { test, expect } from '@playwright/test' +import { expect, test } from '@playwright/test' import { - gotoAppRoute, - navClickTo, + APP_MAIN, collectAppErrors, expectNoAppErrors, - APP_MAIN, -} from './_helpers' + gotoAppRoute, + navClickTo, +} from './_helpers.ts' test('dashboard: renders the overview surface (info box, refresh, statistics tables)', async ({ page, 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 2459c27b..037c59a6 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/features-roadmap.spec.ts b/tests/e2e/spec-coverage/features-roadmap.spec.ts index a46ddd43..69dc47a2 100644 --- a/tests/e2e/spec-coverage/features-roadmap.spec.ts +++ b/tests/e2e/spec-coverage/features-roadmap.spec.ts @@ -12,14 +12,14 @@ * (src/main.js), the resolved manifest is what the router serves, so the * roadmap page renders its real content. */ -import { test, expect } from '@playwright/test' +import { expect, test } from '@playwright/test' import { - gotoAppRoute, + APP_MAIN, + appNav, collectAppErrors, expectNoAppErrors, - appNav, - APP_MAIN, -} from './_helpers' + gotoAppRoute, +} from './_helpers.ts' test('features-roadmap: deep-link route mounts the roadmap surface', async ({ page, diff --git a/tests/e2e/spec-coverage/gemma-faceted-search.spec.ts b/tests/e2e/spec-coverage/gemma-faceted-search.spec.ts index a5fd1c7f..5991b553 100644 --- a/tests/e2e/spec-coverage/gemma-faceted-search.spec.ts +++ b/tests/e2e/spec-coverage/gemma-faceted-search.spec.ts @@ -1,3 +1,6 @@ +import type { APIRequestContext } from '@playwright/test' +import type { VoorzieningenConfig } from '../workflows/_fixtures.ts' + // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2026 Conduction B.V. /** @@ -30,22 +33,20 @@ * * @spec openspec/specs/gemma-faceted-search/spec.md */ -import { test, expect } from '@playwright/test' -import type { APIRequestContext } from '@playwright/test' -import { - APP_MAIN, - collectAppErrors, - expectNoAppErrors, - navClickTo, -} from './_helpers' +import { expect, test } from '@playwright/test' import { - RUN_ID, createObject, deleteObject, newApiContext, resolveConfig, - type VoorzieningenConfig, -} from '../workflows/_fixtures' + RUN_ID, +} from '../workflows/_fixtures.ts' +import { + APP_MAIN, + collectAppErrors, + expectNoAppErrors, + navClickTo, +} from './_helpers.ts' const FACETS = '/index.php/apps/stackiq/api/facets' /** The four GEMMA dimensions the endpoint must always describe. */ @@ -120,7 +121,7 @@ test('facets: the response carries all four GEMMA dimensions, empty ones as [] n // rather than a weaker version of it. for (const dim of DIMENSIONS) { expect( - Object.prototype.hasOwnProperty.call(body, dim), + Object.hasOwn(body, dim), `dimension "${dim}" is missing from the response`, ).toBe(true) expect( diff --git a/tests/e2e/spec-coverage/index-pages.spec.ts b/tests/e2e/spec-coverage/index-pages.spec.ts index 039f56b2..5a863bde 100644 --- a/tests/e2e/spec-coverage/index-pages.spec.ts +++ b/tests/e2e/spec-coverage/index-pages.spec.ts @@ -27,15 +27,15 @@ * blob. See the block above the standards test for why repointing the page was * the right fix and attaching the schema to the catalog register was not. */ -import { test, expect } from '@playwright/test' +import { expect, test } from '@playwright/test' import { - navClickTo, - gotoAppRoute, + APP_MAIN, collectAppErrors, - expectNoAppErrors, expectIndexSurface, - APP_MAIN, -} from './_helpers' + expectNoAppErrors, + gotoAppRoute, + navClickTo, +} from './_helpers.ts' interface IndexPage { /** Exact app-navigation link label. */ diff --git a/tests/e2e/spec-coverage/license-posture.spec.ts b/tests/e2e/spec-coverage/license-posture.spec.ts index 06398742..4e3d423d 100644 --- a/tests/e2e/spec-coverage/license-posture.spec.ts +++ b/tests/e2e/spec-coverage/license-posture.spec.ts @@ -22,14 +22,14 @@ * * @spec openspec/specs/software-license-posture/spec.md */ -import { test, expect } from '@playwright/test' +import { expect, test } from '@playwright/test' import { APP_MAIN, collectAppErrors, expectNoAppErrors, navClickTo, -} from './_helpers' -import { LicensePostureView } from './page-components' +} from './_helpers.ts' +import { LicensePostureView } from './page-components.ts' // @e2e software-license-posture::open-source-vs-closed-source-share-reflects-deployments-not-catalogue-rows test('license posture: nav reaches the dashboard; portfolio share renders', async ({ diff --git a/tests/e2e/spec-coverage/lifecycle-roadmap.spec.ts b/tests/e2e/spec-coverage/lifecycle-roadmap.spec.ts index 1ae85adb..60585bc7 100644 --- a/tests/e2e/spec-coverage/lifecycle-roadmap.spec.ts +++ b/tests/e2e/spec-coverage/lifecycle-roadmap.spec.ts @@ -18,14 +18,14 @@ * * @spec openspec/specs/application-lifecycle-tracking/spec.md */ -import { test, expect } from '@playwright/test' +import { expect, test } from '@playwright/test' import { APP_MAIN, collectAppErrors, expectNoAppErrors, navClickTo, -} from './_helpers' -import { LifecycleRoadmapView } from './page-components' +} from './_helpers.ts' +import { LifecycleRoadmapView } from './page-components.ts' // @e2e application-lifecycle-tracking::roadmap-groups-and-orders-the-portfolio test('roadmap: nav entry reaches the organisation-first roadmap surface', async ({ diff --git a/tests/e2e/spec-coverage/page-surfaces.spec.ts b/tests/e2e/spec-coverage/page-surfaces.spec.ts index 02a7de91..c1cebaa1 100644 --- a/tests/e2e/spec-coverage/page-surfaces.spec.ts +++ b/tests/e2e/spec-coverage/page-surfaces.spec.ts @@ -1,3 +1,5 @@ +import type { Page } from '@playwright/test' + // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2026 Conduction B.V. /** @@ -34,13 +36,13 @@ * @spec openspec/specs/portfolio-rationalization-time/spec.md * @spec openspec/specs/eol-feed-integration/spec.md */ -import { test, expect, type Page } from '@playwright/test' +import { expect, test } from '@playwright/test' import { APP_MAIN, collectAppErrors, expectNoAppErrors, navClickTo, -} from './_helpers' +} from './_helpers.ts' /** * The four GEMMA dimensions `FacetedCatalogIndexView` declares in diff --git a/tests/e2e/spec-coverage/settings.spec.ts b/tests/e2e/spec-coverage/settings.spec.ts index 8814b99a..fadbf25c 100644 --- a/tests/e2e/spec-coverage/settings.spec.ts +++ b/tests/e2e/spec-coverage/settings.spec.ts @@ -25,9 +25,9 @@ * so opening Settings no longer logs "Failed to load users"; collectAppErrors no * longer filters that message, so this suite asserts it is genuinely absent. */ -import { test, expect } from '@playwright/test' -import { collectAppErrors, expectNoAppErrors } from './_helpers' -import { VersionInformation } from './page-components' +import { expect, test } from '@playwright/test' +import { collectAppErrors, expectNoAppErrors } from './_helpers.ts' +import { VersionInformation } from './page-components.ts' /** * Open the app's Nextcloud admin settings section and return its host element. diff --git a/tests/e2e/spec-coverage/suite-wizard.spec.ts b/tests/e2e/spec-coverage/suite-wizard.spec.ts index 797513cc..56f3c99f 100644 --- a/tests/e2e/spec-coverage/suite-wizard.spec.ts +++ b/tests/e2e/spec-coverage/suite-wizard.spec.ts @@ -1,3 +1,7 @@ +import type { APIRequestContext } from '@playwright/test' +import type { Page } from '@playwright/test' +import type { VoorzieningenConfig } from '../workflows/_fixtures.ts' + // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2026 Conduction B.V. /** @@ -26,22 +30,20 @@ * * @spec openspec/specs/suite-wizard/spec.md */ -import { test, expect, type Page } from '@playwright/test' -import { - APP_MAIN, - collectAppErrors, - expectNoAppErrors, - navClickTo, -} from './_helpers' +import { expect, test } from '@playwright/test' import { - RUN_ID, cleanupByToken, createObject, newApiContext, resolveConfig, - type VoorzieningenConfig, -} from '../workflows/_fixtures' -import type { APIRequestContext } from '@playwright/test' + RUN_ID, +} from '../workflows/_fixtures.ts' +import { + APP_MAIN, + collectAppErrors, + expectNoAppErrors, + navClickTo, +} from './_helpers.ts' const APP_A = `Suite member A ${RUN_ID}` const APP_B = `Suite member B ${RUN_ID}` diff --git a/tests/e2e/spec-coverage/vulnerability-tracking.spec.ts b/tests/e2e/spec-coverage/vulnerability-tracking.spec.ts index c128b764..02c0c09c 100644 --- a/tests/e2e/spec-coverage/vulnerability-tracking.spec.ts +++ b/tests/e2e/spec-coverage/vulnerability-tracking.spec.ts @@ -20,14 +20,14 @@ * * @spec openspec/specs/module-vulnerability-tracking/spec.md */ -import { test, expect } from '@playwright/test' +import { expect, test } from '@playwright/test' import { APP_MAIN, collectAppErrors, expectNoAppErrors, navClickTo, -} from './_helpers' -import { KwetsbaarhedenView } from './page-components' +} from './_helpers.ts' +import { KwetsbaarhedenView } from './page-components.ts' // @e2e module-vulnerability-tracking::report-a-vulnerability-affecting-an-application // @e2e module-vulnerability-tracking::the-capability-makes-the-shipped-notification-reachable diff --git a/tests/e2e/visual/_visual-helpers.ts b/tests/e2e/visual/_visual-helpers.ts index a96bb7b5..0d80da57 100644 --- a/tests/e2e/visual/_visual-helpers.ts +++ b/tests/e2e/visual/_visual-helpers.ts @@ -1,3 +1,5 @@ +import type { Locator, Page } from '@playwright/test' + /* * SPDX-License-Identifier: EUPL-1.2 * @@ -24,7 +26,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 { expect } from '@playwright/test' /** Common screenshot options applied to every visual assertion. */ export const SHOT_OPTIONS = { diff --git a/tests/e2e/visual/stackiq.visual.spec.ts b/tests/e2e/visual/stackiq.visual.spec.ts index 28fdbf56..1b833cba 100644 --- a/tests/e2e/visual/stackiq.visual.spec.ts +++ b/tests/e2e/visual/stackiq.visual.spec.ts @@ -13,16 +13,16 @@ // (the bare /apps/stackiq/ route 404s), so navigation targets the // /index entrypoint. import { test } from '@playwright/test' -import { shootSurface, shootByNav } from './_visual-helpers' +import { shootByNav, shootSurface } from './_visual-helpers.ts' const APP = '/index.php/apps/stackiq/index' test.describe('Stackiq — visual baselines', () => { test('dashboard', async ({ page }) => { - await shootSurface(page, `${APP}#/`, 'dashboard.png') + await shootSurface(page, `${APP}/`, 'dashboard.png') }) test('organisations list', async ({ page }) => { - await shootByNav(page, `${APP}#/`, 'Organisations', 'organisations.png') + await shootByNav(page, `${APP}/`, 'Organisations', 'organisations.png') }) }) diff --git a/tests/e2e/workflows/_fixtures.ts b/tests/e2e/workflows/_fixtures.ts index 8df01dc0..ed688de9 100644 --- a/tests/e2e/workflows/_fixtures.ts +++ b/tests/e2e/workflows/_fixtures.ts @@ -23,11 +23,10 @@ * = POST, deleteObject = DELETE on `/api/objects/{register}/{schema}[/{id}]`). */ -import { - request as playwrightRequest, - type APIRequestContext, -} from '@playwright/test' -import { resolveBaseUrl } from '../base-url' +import type { APIRequestContext } from '@playwright/test' + +import { request as playwrightRequest } from '@playwright/test' +import { resolveBaseUrl } from '../base-url.ts' // Re-exported from the single central resolver (tests/e2e/base-url.ts). These // fixtures CREATE organisations and contracts, so a `localhost:8080` fallback @@ -127,7 +126,7 @@ export async function deleteObject( ) if (!res.ok() && res.status() !== 404) { // Non-fatal during cleanup; log only. - // eslint-disable-next-line no-console + console.warn( `deleteObject(${register}/${schema}/${id}) returned ${res.status()}`, ) diff --git a/tests/e2e/workflows/_ui.ts b/tests/e2e/workflows/_ui.ts index 1d65a1d9..231c857a 100644 --- a/tests/e2e/workflows/_ui.ts +++ b/tests/e2e/workflows/_ui.ts @@ -1,3 +1,5 @@ +import type { Locator, Page } from '@playwright/test' + // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2026 Conduction B.V. /** @@ -10,20 +12,20 @@ * NcActions menu with View/Edit/Copy/Delete, the create/edit form fields and * the delete-confirm dialog). No Vue `$data` / `__vue__` patching. */ -import { expect, type Page, type Locator } from '@playwright/test' +import { expect } from '@playwright/test' // `gotoAppRoute` is re-exported alongside `navClickTo` because not every // manifest page has a navigation entry: `/contactpersonen` is routable but was // deliberately dropped from the menu when contact identity moved to the // Nextcloud addressbook, so for that page the route IS the user's real path. export { - navClickTo, - gotoAppRoute, - dismissSupportDialog, + APP_MAIN, collectAppErrors, + dismissSupportDialog, expectNoAppErrors, - APP_MAIN, -} from '../spec-coverage/_helpers' + gotoAppRoute, + navClickTo, +} from '../spec-coverage/_helpers.ts' /** The CnIndexPage main content region. */ export function indexMain(page: Page): Locator { diff --git a/tests/e2e/workflows/crud-persistence.spec.ts b/tests/e2e/workflows/crud-persistence.spec.ts index 41b745ac..b3cf7aa6 100644 --- a/tests/e2e/workflows/crud-persistence.spec.ts +++ b/tests/e2e/workflows/crud-persistence.spec.ts @@ -1,3 +1,7 @@ +import type { Page } from '@playwright/test' +import type { APIRequestContext } from '@playwright/test' +import type { VoorzieningenConfig } from './_fixtures.ts' + // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2026 Conduction B.V. /** @@ -41,27 +45,21 @@ * exactly this run's rows through the OR deleteObject verb and never touches the * pre-existing demo data. */ -import { test, expect, type APIRequestContext } from '@playwright/test' +import { expect, test } from '@playwright/test' +import { cleanupByToken, newApiContext, resolveConfig, RUN_ID } from './_fixtures.ts' import { - navClickTo, - gotoAppRoute, - dismissSupportDialog, + clickAction, collectAppErrors, + dismissSupportDialog, expectNoAppErrors, + gotoAppRoute, indexMain, - showTable, listTotal, + navClickTo, openCreateDialog, openRowActions, - clickAction, -} from './_ui' -import { - newApiContext, - resolveConfig, - cleanupByToken, - RUN_ID, - type VoorzieningenConfig, -} from './_fixtures' + showTable, +} from './_ui.ts' /** * Wait for the edit dialog after an `Edit` row action, wherever it opens. @@ -79,7 +77,7 @@ import { * @param page The Playwright page. * @return The visible edit dialog locator. */ -async function editDialogAfterEdit(page: import('@playwright/test').Page) { +async function editDialogAfterEdit(page: Page) { const dialog = page.locator('[role="dialog"], .modal-container').first() const direct = await dialog .waitFor({ state: 'visible', timeout: 5000 }) @@ -108,7 +106,7 @@ test.beforeAll(async () => { test.afterAll(async () => { if (apiCtx && cfg) { const removed = await cleanupByToken(apiCtx, cfg, RUN_ID) - // eslint-disable-next-line no-console + console.log( `[crud-persistence] cleaned up ${removed} seeded row(s) for ${RUN_ID}`, ) diff --git a/tests/e2e/workflows/org-export-workflow.spec.ts b/tests/e2e/workflows/org-export-workflow.spec.ts index f8e4d01f..08e793b1 100644 --- a/tests/e2e/workflows/org-export-workflow.spec.ts +++ b/tests/e2e/workflows/org-export-workflow.spec.ts @@ -1,3 +1,6 @@ +import type { APIRequestContext, Page } from '@playwright/test' +import type { VoorzieningenConfig } from './_fixtures.ts' + // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2026 Conduction B.V. /** @@ -32,17 +35,16 @@ * AMEF XML body — it activates automatically once the AMEF register is * configured. */ -import { test, expect, type APIRequestContext, type Page } from '@playwright/test' +import { expect, test } from '@playwright/test' import { - newApiContext, - resolveConfig, createObject, deleteObject, findAll, nameOf, + newApiContext, + resolveConfig, RUN_ID, - type VoorzieningenConfig, -} from './_fixtures' +} from './_fixtures.ts' let apiCtx: APIRequestContext let cfg: VoorzieningenConfig diff --git a/tests/e2e/workflows/organisatie-crud.spec.ts b/tests/e2e/workflows/organisatie-crud.spec.ts index b5c14a02..e3fcbf71 100644 --- a/tests/e2e/workflows/organisatie-crud.spec.ts +++ b/tests/e2e/workflows/organisatie-crud.spec.ts @@ -1,3 +1,6 @@ +import type { APIRequestContext } from '@playwright/test' +import type { VoorzieningenConfig } from './_fixtures.ts' + // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2026 Conduction B.V. /** @@ -25,23 +28,22 @@ * Cleanup: the seeded org carries the RUN_ID token; afterAll deletes it via the * OR deleteObject verb. */ -import { test, expect, type APIRequestContext } from '@playwright/test' +import { expect, test } from '@playwright/test' +import { + cleanupByToken, + createObject, + newApiContext, + resolveConfig, + RUN_ID, +} from './_fixtures.ts' import { - navClickTo, - dismissSupportDialog, collectAppErrors, + dismissSupportDialog, expectNoAppErrors, indexMain, + navClickTo, openCreateDialog, -} from './_ui' -import { - newApiContext, - resolveConfig, - createObject, - cleanupByToken, - RUN_ID, - type VoorzieningenConfig, -} from './_fixtures' +} from './_ui.ts' let apiCtx: APIRequestContext let cfg: VoorzieningenConfig @@ -73,7 +75,7 @@ test.beforeAll(async () => { test.afterAll(async () => { if (apiCtx && cfg) { const removed = await cleanupByToken(apiCtx, cfg, RUN_ID) - // eslint-disable-next-line no-console + console.log( `[organisatie-crud] cleaned up ${removed} seeded row(s) for ${RUN_ID}`, ) diff --git a/tests/l10n/check-l10n-parity.js b/tests/l10n/check-l10n-parity.js index 70e99a9a..460ccf46 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 ad441dad..4f06ede0 100644 --- a/tests/l10n/check-l10n.js +++ b/tests/l10n/check-l10n.js @@ -128,7 +128,7 @@ function unescape (s) { const used = new Map() function record (key, file, idx, content) { - if (key == null) { + if ((key === null || key === undefined)) { return } const k = unescape(key) @@ -154,7 +154,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-manifest.js b/tests/validate-manifest.js index 5540a6ca..113a6057 100644 --- a/tests/validate-manifest.js +++ b/tests/validate-manifest.js @@ -83,8 +83,8 @@ function loadJson(file) { } function loadAjv() { - let Ajv2020 = null - let addFormats = null + let Ajv2020 + let addFormats try { Ajv2020 = require('ajv/dist/2020').default || require('ajv/dist/2020') } catch (_) { diff --git a/tests/vitest/adminApi.spec.js b/tests/vitest/adminApi.spec.js index 0f86ee69..12378bb0 100644 --- a/tests/vitest/adminApi.spec.js +++ b/tests/vitest/adminApi.spec.js @@ -7,10 +7,10 @@ * @spec openspec/changes/open-data-publishing/specs/open-data-publishing/spec.md */ -import { describe, it, expect, vi } from 'vitest' +import { describe, expect, it, vi } from 'vitest' import { - apiUrl, apiRequest, + apiUrl, normaliseFederationStatus, } from '../../src/utils/adminApi.js' diff --git a/tests/vitest/complianceMatrix.spec.js b/tests/vitest/complianceMatrix.spec.js index a708da30..d41ef626 100644 --- a/tests/vitest/complianceMatrix.spec.js +++ b/tests/vitest/complianceMatrix.spec.js @@ -11,17 +11,17 @@ * @spec openspec/specs/bio-compliance-assessment/spec.md */ -import { describe, it, expect } from 'vitest' +import { describe, expect, it } from 'vitest' import { + buildComplianceMatrix, + buildOrganisationCoverage, CELL, COLUMN_SOURCE, - resolveUuid, + columnLabel, hasEvidence, partitionCompliancy, - buildComplianceMatrix, - buildOrganisationCoverage, + resolveUuid, standardLabel, - columnLabel, } from '../../src/utils/complianceMatrix.js' describe('complianceMatrix.resolveUuid', () => { diff --git a/tests/vitest/contractCost.spec.js b/tests/vitest/contractCost.spec.js index b9f47674..94a0bab2 100644 --- a/tests/vitest/contractCost.spec.js +++ b/tests/vitest/contractCost.spec.js @@ -4,13 +4,13 @@ * @spec openspec/changes/contract-administration/specs/contract-administration/spec.md */ -import { describe, it, expect } from 'vitest' +import { describe, expect, it } from 'vitest' import { - PERIOD, - parseAmount, annualisedCost, - totalAnnualisedCost, isOneOff, + parseAmount, + PERIOD, + totalAnnualisedCost, } from '../../src/utils/contractCost.js' describe('contractCost.parseAmount', () => { diff --git a/tests/vitest/facetSchema.spec.js b/tests/vitest/facetSchema.spec.js index 7c62927c..eab5ee14 100644 --- a/tests/vitest/facetSchema.spec.js +++ b/tests/vitest/facetSchema.spec.js @@ -17,10 +17,10 @@ * reads the package's own registry. */ -import { describe, it, expect } from 'vitest' -import { buildFacetDimensionSchema } from '../../src/utils/facetSchema.js' +import { describe, expect, it } from 'vitest' // The REAL implementation from the installed package — not a local restatement. import { filtersFromSchema } from '../../node_modules/@conduction/nextcloud-vue/src/utils/schema.js' +import { buildFacetDimensionSchema } from '../../src/utils/facetSchema.js' /** The four GEMMA dimensions FacetedCatalogIndexView declares. */ const DIMENSION_LABELS = { diff --git a/tests/vitest/heartbeat.spec.js b/tests/vitest/heartbeat.spec.js index b3d5979c..e7805f0f 100644 --- a/tests/vitest/heartbeat.spec.js +++ b/tests/vitest/heartbeat.spec.js @@ -9,7 +9,7 @@ * and the withHeartbeat convenience wrapper. */ -import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' // The module instantiates a singleton + reads global OC.requestToken inside // sendHeartbeat(). We stub the globals and the fetch implementation before diff --git a/tests/vitest/licensePosture.spec.js b/tests/vitest/licensePosture.spec.js index 19bdf031..9b453542 100644 --- a/tests/vitest/licensePosture.spec.js +++ b/tests/vitest/licensePosture.spec.js @@ -9,14 +9,14 @@ * @spec openspec/specs/software-license-posture/spec.md */ -import { describe, it, expect } from 'vitest' +import { describe, expect, it } from 'vitest' import { + deploymentCount, LICENSE_TYPE, normaliseLicenseType, - deploymentCount, - portfolioPosture, - perVendorRollup, perOrganisationPosture, + perVendorRollup, + portfolioPosture, } from '../../src/utils/licensePosture.js' // Modules: M1 open (vendor VA), M2 closed (vendor VA), M3 unknown-type (vendor VB), @@ -40,11 +40,13 @@ const modules = [ { id: 'M4', name: 'Shelfware', licentietype: 'Closed source', provider: 'VB' }, ] -const inProd = (extra) => ({ - startDateInProduction: '2025-01-01', - startDateOutPhased: '', - ...extra, -}) +function inProd(extra) { + return { + startDateInProduction: '2025-01-01', + startDateOutPhased: '', + ...extra, + } +} // Usages: M1 deployed twice (O1, O2); M2 once (O1); M3 once (O1); M4 phased out (O1). const usages = [ diff --git a/tests/vitest/lifecyclePhase.spec.js b/tests/vitest/lifecyclePhase.spec.js index a67a9b6b..98726249 100644 --- a/tests/vitest/lifecyclePhase.spec.js +++ b/tests/vitest/lifecyclePhase.spec.js @@ -7,13 +7,13 @@ * @spec openspec/changes/application-lifecycle-tracking/specs/application-lifecycle-tracking/spec.md */ -import { describe, it, expect } from 'vitest' +import { describe, expect, it } from 'vitest' import { - PHASE, - parseDate, derivePhase, endOfSupportState, isEolApproaching, + parseDate, + PHASE, phaseOrder, resolveUuid, } from '../../src/utils/lifecyclePhase.js' diff --git a/tests/vitest/manifestWidgetIcons.spec.js b/tests/vitest/manifestWidgetIcons.spec.js index 3f614dc0..2d274415 100644 --- a/tests/vitest/manifestWidgetIcons.spec.js +++ b/tests/vitest/manifestWidgetIcons.spec.js @@ -21,10 +21,10 @@ * SPDX-License-Identifier: EUPL-1.2 */ -import { describe, it, expect } from 'vitest' import fs from 'fs' import path from 'path' import { fileURLToPath } from 'url' +import { describe, expect, it } from 'vitest' const here = path.dirname(fileURLToPath(import.meta.url)) const repoRoot = path.resolve(here, '../..') diff --git a/tests/vitest/moderationItem.spec.js b/tests/vitest/moderationItem.spec.js index 4cd54c13..fcd60223 100644 --- a/tests/vitest/moderationItem.spec.js +++ b/tests/vitest/moderationItem.spec.js @@ -4,10 +4,10 @@ * @spec openspec/changes/open-data-publishing/specs/open-data-publishing/spec.md */ -import { describe, it, expect } from 'vitest' +import { describe, expect, it } from 'vitest' import { - moderationItemTitle, moderationItemSubtitle, + moderationItemTitle, } from '../../src/utils/moderationItem.js' describe('moderationItemTitle', () => { @@ -25,7 +25,11 @@ describe('moderationItemTitle', () => { }) it('ignores blank/whitespace title fields', () => { - expect(moderationItemTitle({ name: ' ', name: 'Real' })).toBe('Real') + // A blank `name` must fall THROUGH to the next title field. The old + // assertion passed `{ name: ' ', name: 'Real' }`, where the second + // key silently overwrites the first, so the object it actually built + // was `{ name: 'Real' }` and the blank branch was never taken. + expect(moderationItemTitle({ name: ' ', title: 'Real' })).toBe('Real') }) }) diff --git a/tests/vitest/navigationStore.spec.js b/tests/vitest/navigationStore.spec.js index a6e6f6b3..2b6201d1 100644 --- a/tests/vitest/navigationStore.spec.js +++ b/tests/vitest/navigationStore.spec.js @@ -9,8 +9,8 @@ * through a real Pinia instance; console noise is silenced. */ -import { describe, it, expect, beforeEach, vi } from 'vitest' import { createPinia, setActivePinia } from 'pinia' +import { beforeEach, describe, expect, it, vi } from 'vitest' import { useNavigationStore } from '../../src/store/modules/navigation.js' describe('stackiq navigation store', () => { diff --git a/tests/vitest/openDataProjection.spec.js b/tests/vitest/openDataProjection.spec.js index 1ddb211b..ed89f682 100644 --- a/tests/vitest/openDataProjection.spec.js +++ b/tests/vitest/openDataProjection.spec.js @@ -4,11 +4,11 @@ * @spec openspec/changes/open-data-publishing/specs/open-data-publishing/spec.md */ -import { describe, it, expect } from 'vitest' +import { describe, expect, it } from 'vitest' import { - projectOpenData, - isClean, DEFAULT_LICENSE, + isClean, + projectOpenData, } from '../../src/utils/openDataProjection.js' describe('openDataProjection.projectOpenData', () => { diff --git a/tests/vitest/orClient.spec.js b/tests/vitest/orClient.spec.js index d3875c05..189a2898 100644 --- a/tests/vitest/orClient.spec.js +++ b/tests/vitest/orClient.spec.js @@ -7,17 +7,15 @@ * Exact-output assertions on every branch (ADR-025 i18n + multi-tenancy). */ -import { describe, it, expect, beforeEach } from 'vitest' - +import { beforeEach, describe, expect, it } from 'vitest' import { + buildObjectUrl, + buildWriteHeaders, OR_API_BASE, resolveLanguage, withLanguageParam, - buildWriteHeaders, - buildObjectUrl, } from '../../src/composables/orClient.js' - -import { __setLanguage, __resetLanguage } from './stubs/nextcloud-l10n.js' +import { __resetLanguage, __setLanguage } from './stubs/nextcloud-l10n.js' describe('OR_API_BASE', () => { it('points at the OpenRegister object API root', () => { diff --git a/tests/vitest/portfolioReport.spec.js b/tests/vitest/portfolioReport.spec.js index 4451fd1e..da3b7b36 100644 --- a/tests/vitest/portfolioReport.spec.js +++ b/tests/vitest/portfolioReport.spec.js @@ -11,14 +11,14 @@ * @spec openspec/changes/portfolio-rationalization-time/specs/portfolio-rationalization-time/spec.md */ -import { describe, it, expect } from 'vitest' +import { describe, expect, it } from 'vitest' import { - QUADRANT_ORDER, - quadrantColor, + buildCsvExportUrl, cloudTransitionLabel, formatCurrency, groupRowsByQuadrant, - buildCsvExportUrl, + QUADRANT_ORDER, + quadrantColor, } from '../../src/utils/portfolioReport.js' describe('QUADRANT_ORDER', () => { diff --git a/tests/vitest/reviewAggregate.spec.js b/tests/vitest/reviewAggregate.spec.js index 572e53eb..fdbddd1f 100644 --- a/tests/vitest/reviewAggregate.spec.js +++ b/tests/vitest/reviewAggregate.spec.js @@ -5,7 +5,7 @@ * @spec openspec/specs/catalog-ratings/spec.md#requirement-module-and-dienst-detail-pages-must-display-an-aggregate-rating-computed-only-from-approved-reviews */ -import { describe, it, expect } from 'vitest' +import { describe, expect, it } from 'vitest' import { aggregatePath, normaliseAggregate, diff --git a/tests/vitest/reviewForm.spec.js b/tests/vitest/reviewForm.spec.js index 84124354..e5d34473 100644 --- a/tests/vitest/reviewForm.spec.js +++ b/tests/vitest/reviewForm.spec.js @@ -9,12 +9,12 @@ * @spec openspec/specs/catalog-ratings/spec.md#requirement-the-submitting-users-identity-must-be-bound-server-side-and-must-not-be-accepted-from-client-input */ -import { describe, it, expect } from 'vitest' +import { describe, expect, it } from 'vitest' import { - ratingOptions, - isReviewFormValid, buildReviewPayload, buildReviewSubmission, + isReviewFormValid, + ratingOptions, } from '../../src/utils/reviewForm.js' describe('reviewForm.ratingOptions', () => { diff --git a/tests/vitest/sbomVulnerabilityMatch.spec.js b/tests/vitest/sbomVulnerabilityMatch.spec.js index 105c0ff9..89316a18 100644 --- a/tests/vitest/sbomVulnerabilityMatch.spec.js +++ b/tests/vitest/sbomVulnerabilityMatch.spec.js @@ -9,13 +9,13 @@ * @spec openspec/specs/sbom-import/spec.md#requirement-components-are-matched-against-existing-kwetsbaarheden-without-external-calls */ -import { describe, it, expect } from 'vitest' +import { describe, expect, it } from 'vitest' import { - purlPackageName, confirmedMatches, - possibleMatches, matchComponent, matchComponents, + possibleMatches, + purlPackageName, } from '../../src/utils/sbomVulnerabilityMatch.js' describe('sbomVulnerabilityMatch.purlPackageName', () => { diff --git a/tests/vitest/sectionInfoSlot.spec.js b/tests/vitest/sectionInfoSlot.spec.js index a2b7a4a0..34d048b6 100644 --- a/tests/vitest/sectionInfoSlot.spec.js +++ b/tests/vitest/sectionInfoSlot.spec.js @@ -35,9 +35,8 @@ * @spec openspec/specs/fe-shell-navigation/spec.md */ -import { describe, it, expect } from 'vitest' import { mount } from '@vue/test-utils' - +import { describe, expect, it } from 'vitest' import AlwaysVisibleSection from '../../src/components/AlwaysVisibleSection.vue' import CollapsibleSection from '../../src/components/CollapsibleSection.vue' diff --git a/tests/vitest/settingsInfoPanels.spec.js b/tests/vitest/settingsInfoPanels.spec.js index 7d4c1abb..ef90e86c 100644 --- a/tests/vitest/settingsInfoPanels.spec.js +++ b/tests/vitest/settingsInfoPanels.spec.js @@ -41,11 +41,11 @@ * @spec openspec/specs/fe-shell-navigation/spec.md */ -import { describe, it, expect } from 'vitest' -import { readFileSync, readdirSync } from 'fs' -import path from 'path' -import { parse } from '@vue/compiler-sfc' import { compile } from '@vue/compiler-dom' +import { parse } from '@vue/compiler-sfc' +import { readdirSync, readFileSync } from 'fs' +import path from 'path' +import { describe, expect, it } from 'vitest' import * as VueRuntime from 'vue' import { createApp } from 'vue' @@ -106,7 +106,7 @@ function renderFragment(markup) { hoistStatic: false, prefixIdentifiers: true, }) - // eslint-disable-next-line no-new-func + const render = new Function('Vue', code)(VueRuntime) const host = document.createElement('div') diff --git a/tests/vitest/suiteWizard.spec.js b/tests/vitest/suiteWizard.spec.js index e3418534..daaa0190 100644 --- a/tests/vitest/suiteWizard.spec.js +++ b/tests/vitest/suiteWizard.spec.js @@ -7,11 +7,11 @@ * @spec openspec/specs/suite-wizard/spec.md#requirement-the-wizard-must-require-at-least-one-attached-application-before-advancing-past-the-applications-step */ -import { describe, it, expect } from 'vitest' +import { describe, expect, it } from 'vitest' import { - isDetailsStepValid, - isApplicationsStepValid, buildSuitePayload, + isApplicationsStepValid, + isDetailsStepValid, summarizeApplications, } from '../../src/utils/suiteWizard.js' diff --git a/tests/vitest/translationBadge.spec.js b/tests/vitest/translationBadge.spec.js index f8e53d9d..fff2db6f 100644 --- a/tests/vitest/translationBadge.spec.js +++ b/tests/vitest/translationBadge.spec.js @@ -8,10 +8,10 @@ * language. Exact-output assertions on every branch. */ -import { describe, it, expect } from 'vitest' +import { describe, expect, it } from 'vitest' import { - languageName, getSourceLanguage, + languageName, shouldShowTranslationBadge, translationBadge, } from '../../src/utils/translationBadge.js' diff --git a/tests/vitest/vulnerabilityExposure.spec.js b/tests/vitest/vulnerabilityExposure.spec.js index 80c1f8d0..fc05f497 100644 --- a/tests/vitest/vulnerabilityExposure.spec.js +++ b/tests/vitest/vulnerabilityExposure.spec.js @@ -9,13 +9,13 @@ * @spec openspec/specs/module-vulnerability-tracking/spec.md */ -import { describe, it, expect } from 'vitest' +import { describe, expect, it } from 'vitest' import { - isInProduction, affectedModuleIds, computeExposure, - exposureCount, exposedOrganisations, + exposureCount, + isInProduction, } from '../../src/utils/vulnerabilityExposure.js' // A vulnerability affecting module M1. diff --git a/tests/vitest/vulnerabilitySeverity.spec.js b/tests/vitest/vulnerabilitySeverity.spec.js index 852b11f9..03f0a727 100644 --- a/tests/vitest/vulnerabilitySeverity.spec.js +++ b/tests/vitest/vulnerabilitySeverity.spec.js @@ -8,14 +8,14 @@ * @spec openspec/specs/module-vulnerability-tracking/spec.md */ -import { describe, it, expect } from 'vitest' +import { describe, expect, it } from 'vitest' import { - SEVERITY, + deriveSeverity, + matchesSeverity, parseCvss, + SEVERITY, severityFromScore, - deriveSeverity, severityOrder, - matchesSeverity, } from '../../src/utils/vulnerabilitySeverity.js' describe('vulnerabilitySeverity.parseCvss', () => {