Skip to content

Feature/add testing - #1732

Open
duderoot wants to merge 3 commits into
DependencyTrack:mainfrom
duderoot:feature/add-testing
Open

Feature/add testing#1732
duderoot wants to merge 3 commits into
DependencyTrack:mainfrom
duderoot:feature/add-testing

Conversation

@duderoot

@duderoot duderoot commented Aug 5, 2026

Copy link
Copy Markdown

Description

Adds a unit test suite for the frontend. The Jest harness introduced earlier on this branch had no specs, so npm test exited non-zero with "No tests found". This fills it in with 405 tests across 12 suites covering src/shared/, the permissions and filter pill mixins, and three filter pill components, and wires the suite into CI as a blocking job.

No production code under src/ is modified — the change is limited to tests, Jest configuration, the CI workflow, and developer documentation.

Addressed Issue

##1731

Additional Details

Configuration changes required to make the harness usable

The existing config could not actually load the modules under test. Four fixes were needed:

  • transformIgnorePatterns now exempts flexver and lodash-es. src/shared/utils.js imports flexver/dist/module, which is ESM, so Jest's default node_modules exclusion made it fail to parse. The alternative — remapping to the CJS flexver/dist/node build — was rejected because compareVersions would then be tested against a build the app does not ship.
  • @/i18n is mapped to a test double. The real module calls webpack's require.context and issues an HTTP request for the default locale at import time, neither of which works outside the webpack build. This is done via moduleNameMapper rather than a __mocks__ directory because the import is transitive (utils.js@/i18n), which is exactly where a per-file jest.mock() gets forgotten.
  • vue is mapped to the full build. The package entrypoint is dist/vue.runtime.common.js, so string templates in test host components and stubs would not compile.
  • src was added to roots. Without it, files that no spec imports were invisible to the coverage collector, and coverage reported 89.97 % over 14 files. The honest figure across all 32 files matched by collectCoverageFrom is 50.28 %.

Coverage thresholds

Thresholds are the measured values rounded down, not aspirational targets. Because Jest removes any file matched by a path key from the global bucket, per-file carve-outs are limited to the two files where a silent regression is a security problem: shared/permissions.js (authorization gating, 100 %) and shared/utils.js (the isUrlSaveForRedirect open-redirect guard, 95/90/95/95). Everything else stays in the global pool, so the eight mixins and six filter pills that still have no specs remain visible as debt rather than hidden behind a narrowed collectCoverageFrom.

Tests that pin current behaviour

Several assertions document quirks rather than assert ideal behaviour, each with a comment explaining why, so that changing them later is a deliberate and visible decision:

  • common.capitalize() leaves strings of two characters or fewer untouched (capitalize('ab') === 'ab'). This leaks into titleCase('a-b') === 'a b' and into the storage key built by loadUserPreferencesForBootstrapTable for a two-character column (myTableShowid, not myTableShowId).
  • isUrlSaveForRedirect() matches by prefix rather than by path segment, so /projectsomething is accepted.
  • hasPermission() returns undefined for an argument that is neither a string nor an array — there is no else branch.
  • formatSeverityLabel() returns '' for falsy input while its sibling formatters return null.

Two findings surfaced by writing the tests

Both were left alone to keep this change test-only, and are worth separate PRs:

  1. makeAnalysisResponseLabelFormatter is defined in src/shared/common.js but is missing from that module's hand-maintained default export, so it is unreachable from the public API. An export-surface test locks the current key set and documents the omission.
  2. permissionsMixin's PERMISSIONS map is maintained by hand alongside the constants in shared/permissions.js. The test now asserts the map against the constants the shared module actually exports rather than a hardcoded count, so the two cannot drift apart unnoticed.

Checklist

duderoot and others added 3 commits August 5, 2026 00:18
The project had no test runner, so behaviour could only be verified by
hand against a running instance.

Add Jest with jsdom and @vue/test-utils, exposed as `npm test`,
`npm run test-watch` and `npm run test-coverage`. Specs live in `tests/`.

Vue SFC compilation is deliberately left out: the code under test is
plain JavaScript (mixins and shared modules), so the setup avoids
depending on vue-jest.

Generated coverage output is excluded from ESLint and Prettier, which
would otherwise lint the report.

Signed-off-by: duderoot <catalin.patruica@xlab-iq.de>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Jest setup covered plain JavaScript only, so component behaviour could
not be asserted.

Add @vue/vue2-jest so `.vue` files are transformed, and map stylesheet
imports to a stub, since Jest cannot parse CSS and styles carry no
behaviour worth asserting.

Signed-off-by: duderoot <catalin.patruica@xlab-iq.de>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Jest harness added earlier on this branch had no specs. This adds the
scaffolding a real suite needs and the first 405 tests, covering
src/shared, the permissions and filter pill mixins, and three of the
filter pill components.

Config changes needed to make the harness usable:

- flexver ships ESM at flexver/dist/module, so it and lodash-es are
  exempted from transformIgnorePatterns rather than remapped to a CJS
  build the app does not ship.
- @/i18n is mapped to a test double; the real module calls webpack's
  require.context and fetches the default locale at import time.
- vue is mapped to the full build, since the package entrypoint is
  runtime-only and test hosts and stubs use string templates.
- src is added to roots so files no spec imports are still reported as
  uncovered. Without it coverage read 89.97% over 14 files instead of
  50.28% over 32.

Coverage thresholds are the measured values rounded down. permissions.js
and utils.js get their own high floors because they gate authorization
and guard against open redirects; everything else stays in the global
pool so the untested mixins and pills remain visible.

Some tests pin current behaviour rather than ideal behaviour, each with a
comment: capitalize() ignores strings of two characters or fewer,
isUrlSaveForRedirect() matches by prefix rather than by path segment, and
hasPermission() returns undefined for a non-string, non-array argument.

Two things worth a follow-up, both left alone here to keep this
test-only: makeAnalysisResponseLabelFormatter is defined in
shared/common.js but missing from its hand-maintained default export, so
it is unreachable; and permissionsMixin's PERMISSIONS map is asserted
against the constants the shared module exports rather than a fixed
count, so the two cannot drift apart unnoticed.

Unit tests now run as a blocking job in the lint workflow.

Signed-off-by: duderoot <catalin.patruica@xlab-iq.de>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@owasp-dt-bot

Copy link
Copy Markdown

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants