test: add frontend test infrastructure (vitest + vue-test-utils)#50
Open
moodyjmz wants to merge 15 commits into
Open
test: add frontend test infrastructure (vitest + vue-test-utils)#50moodyjmz wants to merge 15 commits into
moodyjmz wants to merge 15 commits into
Conversation
d73ec83 to
888a656
Compare
.nvmrc said 20, package.json's engines.node already said ^24.0.0. The mismatch surfaced while setting up the Vitest harness: Node 25's native localStorage global silently breaks jsdom's implementation under Vitest, Node 24 doesn't have that problem. Signed-off-by: James Manuel <moodyjmz@users.noreply.github.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
vitest + @vue/test-utils + jsdom, sharing the app's Vite/Vue transform pipeline via @vitejs/plugin-vue. Shared mocks for the four Nextcloud runtime globals (l10n, auth, initial-state, router) and a ResizeObserver stub live in vitest.setup.ts so future specs don't re-stub them. Adds a makeNode() fixture factory (src/test-utils/fixtures.ts) so owner/mount-type permutations — the axis the Mine filter and external storage exclusion depend on — are a one-line call, not a hand-built Node. Spec glob is src/**/*.spec.ts; no specs yet. Signed-off-by: James Manuel <moodyjmz@users.noreply.github.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
padded-blocks in the ResizeObserver stub class, and a JSDoc block on makeNode() that eslint-plugin-jsdoc wanted full @param tags for — a plain comment says the same thing without the noise. Signed-off-by: James Manuel <moodyjmz@users.noreply.github.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Move validateFilename() to src/utils/validateFilename.ts and categoryName()/categoryMimes() (with MIME_CATEGORIES/ALL_OFFICE_MIMES) to src/utils/fileCategories.ts. Behaviour-preserving — OfficeOverview.vue calls the same functions, just imported instead of defined inline. filterFiles (the Mine-filter logic) is extracted separately, with #46's external-storage exclusion fix applied. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: James Manuel <moodyjmz@users.noreply.github.com>
Covers config.ts (localStorage round-trip), officeFiles.ts (filterByMimes, DAV SEARCH caching/invalidation, XML escaping), templates.ts (OCS envelope unwrapping), the newly-extracted validateFilename/fileCategories utils, and FileCard.vue (first component mount, zero Nextcloud globals to stub). Adds makeCreator() to the fixture factory alongside makeNode(). filterFiles and OfficeOverview.vue coverage follow in later commits. Also adds an eslintrc override: n/no-unpublished-import doesn't apply to *.spec.ts, which legitimately import test-only devDependencies. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: James Manuel <moodyjmz@users.noreply.github.com>
Move filteredFiles' mine/shared/all + search + sortNodes pipeline to filterFiles() in src/utils/fileFilters.ts. currentUid becomes a parameter instead of a module-scope read. Includes the external-storage mount-type exclusion from #46: most storage backends' getOwner() defaults to whoever is currently browsing when there's no single real owner, so external/external-session mounts must be excluded from "Mine" the same way group/shared mounts are. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: James Manuel <moodyjmz@users.noreply.github.com>
Table-tests mine/shared/all, search-on-top-of-filter, category filtering, and sort. The regression case for #46: a file owned by currentUid on an external/external-session mount is excluded from "mine". Also pins a real, surprising, pre-existing (not introduced here) sortNodes() behaviour: sortingMode 'mtime' internally flips sortingOrder, so 'desc' actually sorts non-favourites oldest-first. Characterizing what main already does, not endorsing it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: James Manuel <moodyjmz@users.noreply.github.com>
Mounts the real component via shallowMount() with the standard
resetModules()+dynamic-import dance (module-level getCurrentUser()/
fetchAll() side effects, officeFiles.ts's cachedNodes singleton).
Covers rendering states (loading/empty/error/no-results/grid-vs-list)
and wiring (openFile, openInFiles, toggleViewMode, doCreateFromTemplate).
Component lookups match by { name }, not by imported reference:
resetModules() gives the freshly re-imported OfficeOverview.vue its own
module instances, so an identity-based findComponent(RealComponent)
silently fails to match.
Harness extensions this needed, all in vitest.config.ts/setup.ts so
future specs get them for free:
- css: true and inlining @nextcloud/vue/router/files — they self-import
CSS (breaks under Node's ESM loader otherwise), and leaving consumers
inlined while @nextcloud/router stayed externalized meant
@nextcloud/files/dav.mjs resolved the real router instead of the
mocked one.
- @nextcloud/l10n and @nextcloud/router mocks now preserve the rest of
each module via importOriginal() — @nextcloud/vue's internals need
more than the one function each we were overriding (isRTL,
generateRemoteUrl, ...).
- config.global.renderStubDefaultSlot, plus a generic
stubRenderingAllSlots() helper for components whose *named* slots
matter (NcAppNavigation's #search, NcEmptyContent's #description) —
shallowMount's auto-stubs render neither by default.
- Top-level @nextcloud/vue layout components (NcContent/NcAppNavigation/
NcAppContent) need real Nextcloud page globals/DOM structure to mount
for real; confirmed empirically, hence shallowMount over mount().
fixtures.ts's makeNode() now sets id (Node.fileid only derives from a
numeric backing id) — without it every fileid-based assertion was
silently comparing "undefined" to "undefined".
Two real, pre-existing product behaviours pinned as characterization,
not fixed (out of scope for a behaviour-preserving refactor):
- sortNodes() with sortingMode 'mtime' internally flips sortingOrder,
so the "desc" call already in main sorts non-favourites oldest-first.
- If getTemplates() itself rejects, creators stays empty and the UI
shows "No office suite installed", not the "Failed to load files"
error state — that only reaches the error branch when the second
fetch (getAllOfficeFiles) is what fails. Misleading for a network
error, but not this refactor's problem to fix.
Signed-off-by: James Manuel <moodyjmz@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: James Manuel <moodyjmz@users.noreply.github.com> (cherry picked from commit eec63a3) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
js/ and css/ are generated bundles, committed by the nextcloud-command bot via /compile in CI, never by hand. A tracked .githooks/pre-commit rejects staged js/css on dev machines; it steps aside when CI=true so the bot's asset commit is unaffected. Wired through a 'prepare' script so it auto-installs on npm install, no husky dependency. See README 'Committing changes'. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: James Manuel <moodyjmz@users.noreply.github.com>
888a656 to
94f89fd
Compare
Full mount (not shallow) — NcButton/NcIconSvgWrapper need no page-level Nextcloud globals to render, unlike OfficeOverview.vue's NcContent/NcAppNavigation. Covers items (blank card always first), nameWithoutExt (ordinary name, dotfile, no-extension), click wiring (blank vs template card select args), template preview (previewUrl present/absent, broken-image fallback to icon), and themeType/palette mapping (known mime, unmapped mime, empty mimetypes). Scroll-arrow tests need scrollLeft/scrollWidth/clientWidth overridden via Object.defineProperty (jsdom reports these as 0 by default) and Element.prototype.scrollBy stubbed (jsdom doesn't implement it at all), then assert canScrollLeft/canScrollRight and the scroll-step math: max(CARD_WIDTH+CARD_GAP, clientWidth-(CARD_WIDTH+CARD_GAP)). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: James Manuel <moodyjmz@users.noreply.github.com>
One-line wrapper (<OfficeOverview />) — a single shallowMount smoke test is proportionate. Stubs templates.ts/officeFiles.ts locally since OfficeOverview.vue still runs fetchAll() at module-evaluation time. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: James Manuel <moodyjmz@users.noreply.github.com>
Same three-job changes/test/summary shape as the existing lint-eslint.yml/npm-build.yml workflows, same action pins — this repo already has a convention for path-filtered PR checks. Path filter deliberately isn't src/** alone: a vitest.config.ts or setup-file change with no src/ diff would otherwise skip the check while still being able to break every test. Watches .github/workflows/**, src/**, vitest.config.ts, vitest.setup.ts, package.json, package-lock.json and tsconfig.json. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: James Manuel <moodyjmz@users.noreply.github.com>
Add a "Running tests" section (npm run test:unit, why nvm use matters — Node 25's native localStorage breaks jsdom under Vitest) and note the pre-commit hook that blocks staged js/css under "Committing changes". Update the architecture diagram to show TemplateSection.vue/ FileCard.vue and the src/utils/ extraction as their own nodes instead of everything living inside OfficeOverview.vue. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: James Manuel <moodyjmz@users.noreply.github.com>
NcEmptyContent's description is a named slot (#description), never a prop — stubRenderingAllSlots' generic slot-forwarding template already handles it regardless of what's declared in props. Listing it there did nothing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: James Manuel <moodyjmz@users.noreply.github.com>
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.
Summary
Stands up frontend test infrastructure and covers all of current
main'ssrc/. Closes #47.@vue/test-utils+ jsdom. Standalonevitest.config.ts(leavesvite.config.tsuntouched to avoid conflict withfeat/wopi-phase-5), sharedvitest.setup.ts(Nextcloud global mocks +ResizeObserverstub), amakeNode()/makeCreator()fixture factory, and.nvmrcpinned to Node 24.OfficeOverview.vueintosrc/utils/:filterFiles(mine/shared/all + search + sort),validateFilename, and the category helpers. This makes the logic unit-testable without mounting and turnscurrentUidinto a parameter instead of a module-scope read.src/:config,officeFiles,templates, the extracted utils,FileCard,TemplateSection(including theResizeObserver/scrollLeftjsdom gotchas),OfficeOverviewrendering/wiring, andApp. Includes the regression pin for the external-storage "Mine" fix ("Mine" filter shows external storage files as owned by everyone #46): an external-storage file owned by the current user is excluded frommine..github/workflows/test-unit.yml, same path-filteredchanges/job/summaryshape as the existing lint workflows, runningnpm run test:uniton PRs.preparescript) that blocks hand-committedjs//css/assets, plus a README note documenting the source-only //compileconvention.Notes for reviewers
npm-buildwill go red, and that's expected — this is a source-only PR; the built assets come from the/compilebot, per the README "Committing changes" section added here.🤖 Generated with Claude Code