chore(deps): @nextcloud/router 2 -> 3, and complete 33 incomplete test mocks - #296
Merged
Merged
Conversation
…t mocks
The library upgrade is trivial -- openbuild uses exactly two of its exports,
`generateUrl` and `imagePath`, and v3 still exports both. Unlike @nextcloud/vue
v9 or @nextcloud/dialogs v7, v3 keeps `main` plus BOTH `import` and `require`
conditions, so there is no resolution work either.
What blocked this PR was 33 failing test files, and they were not the library's
fault.
Error: [vitest] No "imagePath" export is defined on the
"@nextcloud/router" mock. Did you forget to return it from "vi.mock"?
57 spec files mock @nextcloud/router, and most declared only what they
personally cared about:
vi.mock('@nextcloud/router', () => ({ generateUrl: (p) => p }))
But a `vi.mock` factory replaces the module for the WHOLE module graph of that
test, not just for the spec's own imports. Any component reachable from the test
that imports `imagePath` gets a module where it does not exist. src has been
importing `imagePath` all along -- so these mocks have been incomplete since they
were written.
Why it only surfaces now: v2 was resolved as CJS, and vitest models a mocked CJS
module as a proxy that answers `undefined` for anything the factory omitted.
v3 declares `"type": "module"` and is resolved as ESM, where a missing named
export is an error rather than an undefined. The upgrade did not break the
mocks; it stopped hiding that they were broken.
Fixed with `importOriginal` rather than by adding `imagePath`
-------------------------------------------------------------
Every one of the 33 now spreads the real module and overrides only what it
means to stub:
vi.mock('@nextcloud/router', async (importOriginal) => ({
...(await importOriginal()),
generateUrl: (p) => p,
}))
Adding `imagePath: () => ...` to each would have fixed today's failure and left
the same trap for the next export any component starts using. Spreading the
original cannot rot: the stub stays deliberate, everything else stays real.
Files that stub more than one function (ThemePickerDialog, the two
`{slug}`-expanding GitHub modals) keep their own overrides on top of the spread.
Measured, not assumed
---------------------
The test COUNT is the part worth reading, not the pass/fail:
before (router 3, old mocks): 33 files failed, 1125 tests ran
after: 141 files passed, 1378 tests ran
Those 33 files errored during collection, so their 253 tests never executed at
all. A file that fails to load reports as one red file, not 253 missing tests --
the count is the only place the difference is visible.
Verified with the commands CI runs
----------------------------------
npm ci rc=0
npm run build rc=0, 0 errors (3 pre-existing size warnings)
npx vitest run 141/141 files, 1378/1378 tests
npm run lint rc=0
npm run stylelint rc=0
npx prettier --check rc=0
check:manifest, check:gitignore, check:nc-floor, test:l10n all rc=0
One caveat, stated rather than smoothed over: immediately after the cold
`npm ci` reinstall, one run reported `1 failed | 1377 passed`. Four subsequent
full runs were clean (1378/1378), and I did not capture which test it was before
the log was overwritten, so I cannot attribute it. It is not a mock failure --
those fail at collection and take a whole file with them, which this did not. I
am flagging it as an unidentified one-off rather than calling the suite
deterministic on four green runs.
Closes #246
Contributor
Quality Report — ConductionNL/openbuild @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| test-l10n | ✅ | ||||
| check-gitignore | ✅ | ||||
| check-nc-floor | ✅ | ||||
| format | ✅ | ||||
| composer | ✅ | ✅ 106/106 | |||
| npm | ✅ | ✅ 625/625 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-08-21 05:13 UTC
Download the full PDF report from the workflow artifacts.
rubenvdlinde
pushed a commit
that referenced
this pull request
Aug 21, 2026
# Conflicts: # package-lock.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
chore(deps): @nextcloud/router 2 -> 3, and complete 33 incomplete test mocks
The library upgrade is trivial -- openbuild uses exactly two of its exports,
generateUrlandimagePath, and v3 still exports both. Unlike @nextcloud/vuev9 or @nextcloud/dialogs v7, v3 keeps
mainplus BOTHimportandrequireconditions, so there is no resolution work either.
What blocked this PR was 33 failing test files, and they were not the library's
fault.
57 spec files mock @nextcloud/router, and most declared only what they
personally cared about:
But a
vi.mockfactory replaces the module for the WHOLE module graph of thattest, not just for the spec's own imports. Any component reachable from the test
that imports
imagePathgets a module where it does not exist. src has beenimporting
imagePathall along -- so these mocks have been incomplete since theywere written.
Why it only surfaces now: v2 was resolved as CJS, and vitest models a mocked CJS
module as a proxy that answers
undefinedfor anything the factory omitted.v3 declares
"type": "module"and is resolved as ESM, where a missing namedexport is an error rather than an undefined. The upgrade did not break the
mocks; it stopped hiding that they were broken.
Fixed with
importOriginalrather than by addingimagePathEvery one of the 33 now spreads the real module and overrides only what it
means to stub:
Adding
imagePath: () => ...to each would have fixed today's failure and leftthe same trap for the next export any component starts using. Spreading the
original cannot rot: the stub stays deliberate, everything else stays real.
Files that stub more than one function (ThemePickerDialog, the two
{slug}-expanding GitHub modals) keep their own overrides on top of the spread.Measured, not assumed
The test COUNT is the part worth reading, not the pass/fail:
Those 33 files errored during collection, so their 253 tests never executed at
all. A file that fails to load reports as one red file, not 253 missing tests --
the count is the only place the difference is visible.
Verified with the commands CI runs
npm ci rc=0
npm run build rc=0, 0 errors (3 pre-existing size warnings)
npx vitest run 141/141 files, 1378/1378 tests
npm run lint rc=0
npm run stylelint rc=0
npx prettier --check rc=0
check:manifest, check:gitignore, check:nc-floor, test:l10n all rc=0
One caveat, stated rather than smoothed over: immediately after the cold
npm cireinstall, one run reported1 failed | 1377 passed. Four subsequentfull runs were clean (1378/1378), and I did not capture which test it was before
the log was overwritten, so I cannot attribute it. It is not a mock failure --
those fail at collection and take a whole file with them, which this did not. I
am flagging it as an unidentified one-off rather than calling the suite
deterministic on four green runs.
Closes #246