Skip to content

Populate fields.file_path in the JS reporters - #14

Merged
prostoandrei merged 4 commits into
mainfrom
feat/file-path-field
Sep 11, 2026
Merged

prostoandrei merged 4 commits into
mainfrom
feat/file-path-field

Conversation

@prostoandrei

@prostoandrei prostoandrei commented Sep 11, 2026 •

Copy link
Copy Markdown
Contributor

fields.file_path is the key the server joins a requirement's repo_file anchors against (test_link_derive.go). No JS reporter ever set it — only the Go CLI and the PHPUnit reporter do — so a case reported from Playwright or Vitest could not be linked to a requirement by file anchor at all. Every file_path in this repo today belongs to attachments, not to result fields.

Found while asking how the tests/api Playwright suite in qase-tms/app#3883 could reach requirements. It can't, and this is half the reason.

What's here

commons gains resolveFilePath(), deliberately separate from normalizeSpecPath() rather than a reuse of it. normalizeSpecPath returns a path outside the root unchanged — correct for a signature segment, wrong here. An absolute machine path, or a virtual module id, can never match a repo-relative anchor: reporting one yields a field that looks like a join key and silently never joins. undefined means omit, and the caller warns once per file. Omitting is not fabricating — it is what the Go CLI's filemap and the PHPUnit reporter's FilePathResolver already do.

All three reporters (playwright, vitest, jest) resolve the test's own source file against rootDir / TIDEN_ROOT_DIR. A file_path the test set for itself always wins — it may deliberately point at the source under test rather than at the spec's own location.

Incidental fix: the Vitest and Jest builders assigned metadata.fields by reference, so writing file_path onto the result would have mutated the accumulator's metadata. They now copy. An existing test caught this by asserting exact field equality; that assertion is updated to the new contract rather than worked around.

What this deliberately does not do

  • No run-blocking escalation. The PHPUnit reporter refuses to complete a run when zero results resolved a path (plan D10), because a container root mismatch loses every link silently. That is a larger behavioural change and is not in here.
  • It cannot detect a path that resolves against the wrong root. Playwright run from tests/api with no TIDEN_ROOT_DIR yields Tests/v1/x.api.spec.ts — well-formed, matching no anchor. Documented on the function; consumers whose cwd is not the repo root must set TIDEN_ROOT_DIR.
  • This alone does not link the API suite. Derive is a colocation heuristic (directory agreement + file stem, or the __tests__ convention). Black-box HTTP tests in a separate tree still need spec-file anchors on the requirements, or explicit ingest.

Verification

Unit tests cover the resolver and both builders: relative resolution, outside-root omission, virtual module ids, warn-once-per-file, user-set value wins, and no-location/no-module.

Full suites green — commons 505, jest 77, playwright 105, vitest 109. Typecheck at its pre-existing 101-error baseline with zero errors in src/. Two new lint warnings, both the "unnecessary optional chain" that specPath already carries three lines below; the chain is load-bearing for the test doubles, and eslint . sets no --max-warnings.

Unit tests don't exercise the reporter.ts → ResultBuilder rootDir wiring, so I ran a real Playwright suite through the built reporter in report mode:

TIDEN_ROOT_DIR emitted file_path
unset suite/nested/demo.spec.ts
repo root (one level up) .tmp-fp-e2e/suite/nested/demo.spec.ts
/nowhere omitted, with the warning naming the file

Dogfooded

Reported through @tiden/jest-reporter into Tiden run 1413, attached to the intent session on intent/2026-09-11-populate-fields-file_path-in: 794 cases, 794 passed, and every sampled result carries a repo-relative file_path — including the new cases in all three reporter workspaces. That run is this change verifying itself.

Downstream

Packages are published at 0.2.0 and qase-tms/app's tests/api pins "0.2.0" exactly, so this needs a version bump and a release tag before it changes anything there.

Tiden intent branch: intent/2026-09-11-populate-fields-file_path-in

`fields.file_path` is the key the server joins a requirement's repo_file
anchors against (test_link_derive.go). No JS reporter ever set it, so a
case reported from playwright or vitest could not be linked to a
requirement by file anchor at all -- only the Go CLI and the PHPUnit
reporter populated it.

commons gains resolveFilePath(), deliberately separate from
normalizeSpecPath(): that one returns a path outside the root unchanged,
which is right for a signature segment and wrong here. An absolute
machine path or a virtual module id can never match a repo-relative
anchor, so it would be a field that looks like a join key and silently
never joins. Undefined means omit, and the caller warns once per file --
omitting is not fabricating, matching the Go CLI's filemap and the
PHPUnit reporter's FilePathResolver.

Both reporters resolve against rootDir / TIDEN_ROOT_DIR, and a file_path
the test set for itself always wins.

Incidental fix: vitest's builder assigned metadata.fields by reference,
so writing file_path onto the result would have mutated the
accumulator's metadata. It now copies.

Note what this cannot detect: a path that resolves cleanly against the
wrong root. Playwright run from tests/api with no TIDEN_ROOT_DIR yields
`Tests/v1/x.api.spec.ts` -- well-formed, matching no anchor.
The first commit covered playwright and vitest and missed the third
reporter in this repo. @tiden/jest-reporter never set fields.file_path
either -- its own file_path occurrences are all attachment paths -- so
jest-reported cases stayed unlinkable while the other two became
linkable, which is a worse state than either doing it or neither.

Same rule as the others: resolveFilePath against rootDir /
TIDEN_ROOT_DIR, omit rather than report an unmatchable absolute path,
warn once per file, and never overwrite a file_path the test set for
itself. The builder already had rootDir plumbed in for the signature.

applyMetadata aliased metadata.fields the same way vitest's builder did,
so it copies now for the same reason.
Addresses the review of #14.

1 (high) resolveFilePath compared raw strings, so three plausible
spellings of the same directory silently turned the feature off for a
whole run: a relative root (`..`, `.`) matched no absolute test path, an
unnormalized file path passed through verbatim, and a symlinked root --
/tmp/x on macOS while the runner emits /private/tmp/x -- mismatched.
Relative roots now resolve against cwd, `.`/`..` collapse on both sides,
and realpath is consulted only after the plain comparison fails, memoized
per root. repos/phpunit's FilePathResolver already did this, naming the
same macOS case.

NOT applied to normalizeSpecPath/resolveRootDir as the review suggested:
those feed the case SIGNATURE, and changing how a root resolves would
change the file segment of every signature computed under a relative or
symlinked root, forking the history of every case already reported. A
join key can be corrected; an identity cannot.

2 (medium) rootDir '' reached rootPrefix('') === '/' and reported the
absolute path minus its leading slash -- the "well-formed key that never
joins" this function exists to prevent -- while normalizePath fell back
to cwd, so signature and file_path measured from different roots. An
empty root is now cwd, and the three reporters pass rootDir straight
through instead of branching on `=== undefined`.

3 (medium) a test-set file_path was forwarded unchecked, so a stale or
copy-pasted value could link a case to the wrong requirement. It is now
held to the same standard a derived one is: commons gains
isUsableFilePath, and an absolute or escaping value is replaced by the
derived path with a warning. The larger question -- whether a test should
override file_path at all, which tiden-phpunit-reporter answers no -- is
a product decision and is left open.

4 (low) vitest: splitFullName's and specPath's doc comments had stacked
above filePath, documenting the wrong contract. Each is back on its own
function.

5 (low) CHANGELOG entries for all four packages and a file_path row in
the jest/vitest README option tables.

The three reporters' near-identical helpers are now one applyFilePath so
the rule cannot drift between them.
All four published packages move together -- release.yml refuses a tag
whose version any of commons/playwright/vitest/jest is not at.

The reporters' dependency on commons goes to ^0.3.0 with them. This is
the part that is not bookkeeping: a caret range on a 0.x version is
minor-locked, so ^0.2.0 does NOT admit 0.3.0. Published 0.3.0 reporters
carrying ^0.2.0 would resolve commons 0.2.x, which has no
resolveFilePath or isUsableFilePath export -- every consumer would fail
at import time, on the registry, with the local workspace symlink hiding
it here.

CHANGELOG ## Unreleased sections become ## 0.3.0.
@prostoandrei prostoandrei self-assigned this Sep 11, 2026
@prostoandrei
prostoandrei requested review from n3r and nipisarev and removed request for nipisarev September 11, 2026 10:36
@prostoandrei
prostoandrei merged commit e6ccd02 into main Sep 11, 2026
15 checks passed
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