Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 134 additions & 8 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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',
{
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 3 additions & 0 deletions scripts/build-l10n-js.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ function renderJs(id, translations, pluralForm) {
].join('\n')
}

/**
*
*/
function main() {
const check = process.argv.includes('--check')
const id = appId()
Expand Down
3 changes: 3 additions & 0 deletions scripts/check-schema-l10n.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
2 changes: 1 addition & 1 deletion scripts/check-vue-demi.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
9 changes: 1 addition & 8 deletions src/utils/moderationItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
10 changes: 6 additions & 4 deletions tests/e2e/global-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -91,7 +93,7 @@ export default async function globalSetup(config: FullConfig): Promise<void> {
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.
}
})
Expand Down
6 changes: 4 additions & 2 deletions tests/e2e/manifest-pages.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 4 additions & 7 deletions tests/e2e/org-archimate-export.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 1 addition & 2 deletions tests/e2e/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, '..', '..')

Expand Down
16 changes: 9 additions & 7 deletions tests/e2e/sbom-import.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -109,7 +111,7 @@ async function openComponentsTab(page: Page): Promise<void> {
// 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
Expand Down
2 changes: 1 addition & 1 deletion tests/e2e/smoke/app-mounts.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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/' },
Expand Down
6 changes: 4 additions & 2 deletions tests/e2e/spec-coverage/_helpers.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { Page } from '@playwright/test'

// SPDX-License-Identifier: EUPL-1.2
// SPDX-FileCopyrightText: 2026 Conduction B.V.
/**
Expand All @@ -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 —
Expand Down
32 changes: 14 additions & 18 deletions tests/e2e/spec-coverage/catalog-ratings.spec.ts
Original file line number Diff line number Diff line change
@@ -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.
/**
Expand Down Expand Up @@ -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,
Expand All @@ -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. */
Expand Down
Loading
Loading