Skip to content

chore(deps): @nextcloud/router 2 -> 3, and complete 33 incomplete test mocks - #296

Merged
rubenvdlinde merged 1 commit into
developmentfrom
chore/nextcloud-router-3
Aug 21, 2026
Merged

chore(deps): @nextcloud/router 2 -> 3, and complete 33 incomplete test mocks#296
rubenvdlinde merged 1 commit into
developmentfrom
chore/nextcloud-router-3

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

chore(deps): @nextcloud/router 2 -> 3, and complete 33 incomplete test 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

…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
@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/openbuild @ 4b132b6

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
rubenvdlinde merged commit c56dfad into development Aug 21, 2026
80 checks passed
rubenvdlinde pushed a commit that referenced this pull request Aug 21, 2026
# Conflicts:
#	package-lock.json
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.

1 participant