From d4cea8fbdbddafd2fe4f220233ba030e19d7e09c Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 11 Oct 2026 07:27:23 +0000 Subject: [PATCH 1/5] test: split vitest into projects, switch DOM tests to happy-dom, drop unnecessary tests - Core vitest config now has unit (node, isolate: false), isolated, dom (happy-dom), integration and types projects. Node tests share one module graph per worker instead of re-importing the server graph for every file. - The file extension picks the environment: DOM tests are *.test.tsx. The `// @vitest-environment` pragmas are gone. - upload.ts no longer imports the whole date-fns barrel. - Real 2s reconnect waits use fake timers; the package build test builds once for its read-only assertions. - Delete tests that grep source text, test AutoForm/form-field UI, Base UI wrappers, constants or wiring of features already covered elsewhere. - The blog plugin defaults to node; only its DOM test opts into jsdom. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015x559AHZ5rNsyvKusi5pVk --- AGENTS.md | 9 + .../src/plugin/validation.test.ts | 16 - packages/vitnode/package.json | 10 +- .../scripts/cli/builder/analysis.test.ts | 1 - .../scripts/cli/builder/measure.test.ts | 1 - .../scripts/cli/builder/output-files.test.ts | 1 - .../scripts/cli/builder/package-build.test.ts | 65 ++-- .../scripts/cli/builder/scripts-build.test.ts | 1 - .../scripts/cli/builder/size-severity.test.ts | 1 - .../scripts/cli/builder/snapshot.test.ts | 1 - .../scripts/cli/commands/build.test.ts | 1 - .../vitnode/scripts/cli/commands/db.test.ts | 1 - .../vitnode/scripts/cli/commands/dev.test.ts | 1 - .../vitnode/scripts/cli/commands/i18n.test.ts | 1 - .../scripts/cli/commands/plugin.test.ts | 1 - .../scripts/cli/commands/start.test.ts | 1 - packages/vitnode/scripts/cli/db/db.test.ts | 1 - .../scripts/cli/dev/package-watch.test.ts | 1 - packages/vitnode/scripts/cli/index.test.ts | 1 - .../scripts/cli/plugins/workspace.test.ts | 1 - .../cli/project/package-manager.test.ts | 1 - .../scripts/cli/project/runtime.test.ts | 1 - .../vitnode/scripts/cli/ui/format.test.ts | 1 - .../vitnode/scripts/cli/ui/terminal.test.ts | 1 - packages/vitnode/scripts/cli/ui/ui.test.ts | 1 - .../scripts/database-bootstrap.test.ts | 1 - packages/vitnode/scripts/i18n-check.test.ts | 1 - .../vitnode/scripts/no-route-copier.test.ts | 137 -------- .../scripts/write-plugin-api-registry.test.ts | 1 - .../src/api/admin-staff-permission.test.ts | 1 - .../vitnode/src/api/lib/ai/accounting.test.ts | 1 - .../vitnode/src/api/lib/ai/registry.test.ts | 1 - .../vitnode/src/api/lib/ai/runner.test.ts | 1 - .../vitnode/src/api/lib/auth-cookie.test.ts | 1 - .../lib/content-modules.integration.test.ts | 1 - .../vitnode/src/api/lib/error-details.test.ts | 1 - .../vitnode/src/api/lib/file-download.test.ts | 1 - .../src/api/lib/image-signature.test.ts | 1 - .../src/api/lib/navigation-presets.test.ts | 1 - .../vitnode/src/api/lib/openapi-tags.test.ts | 1 - .../src/api/lib/pagination-cursor.test.ts | 1 - packages/vitnode/src/api/lib/plugin.test.ts | 1 - .../vitnode/src/api/lib/remote-image.test.ts | 1 - .../api/lib/response-cache-headers.test.ts | 1 - .../src/api/lib/sso-avatar-import.test.ts | 1 - .../api/lib/staff-permission-cache.test.ts | 1 - .../src/api/lib/with-pagination.test.ts | 1 - .../api/middlewares/global.middleware.test.ts | 43 --- .../vitnode/src/api/models/device.test.ts | 1 - .../vitnode/src/api/models/events.test.ts | 1 - packages/vitnode/src/api/models/queue.test.ts | 1 - .../vitnode/src/api/models/search.test.ts | 1 - .../src/api/models/session-admin.test.ts | 1 - .../src/api/models/session-revoke.test.ts | 1 - .../src/api/models/sso-link-token.test.ts | 1 - .../src/api/models/storage-image.test.ts | 1 - .../routes/search-permissions.route.test.ts | 1 - .../debug/routes/search-status.route.test.ts | 1 - .../files/routes/download-access.test.ts | 89 ------ .../navigation/lib/read-navigation.test.ts | 1 - .../admin/navigation/lib/reorder.test.ts | 1 - .../admin/roles/routes/delete.route.test.ts | 1 - .../admin/roles/routes/list.route.test.ts | 1 - .../admin/routes/session.route.test.ts | 1 - .../staff/routes/staff-entry-routes.test.ts | 1 - .../lib/assert-edit-user-permission.test.ts | 1 - .../admin/users/routes/devices.route.test.ts | 1 - .../notification-preferences.route.test.ts | 1 - .../admin/users/routes/passkeys.route.test.ts | 1 - .../admin/users/routes/password.route.test.ts | 1 - .../admin/users/routes/show.route.test.ts | 129 -------- .../admin/users/routes/sso.route.test.ts | 1 - .../modules/ai/routes/stream.route.test.ts | 1 - .../helpers/execute-content-schedule.test.ts | 1 - .../api/modules/cron/cron/clean.cron.test.ts | 23 -- .../src/api/modules/middleware/route.test.ts | 1 - .../modules/pages/page-layout-routes.test.ts | 1 - .../search/routes/timeline.route.test.ts | 1 - .../modules/users/passkeys/passkeys.test.ts | 1 - .../routes/credentials-hardening.test.ts | 1 - .../users/routes/devices.route.test.ts | 1 - .../users/routes/email-verification.test.ts | 1 - .../users/routes/password-sign-in.test.ts | 1 - .../users/routes/revoke-device.test.ts | 1 - .../users/routes/session.route.test.ts | 1 - .../users/routes/time-zone.route.test.ts | 1 - .../users/sso/connections/connections.test.ts | 1 - .../modules/users/sso/routes/callback.test.ts | 1 - .../vitnode/src/blocks/boundaries.test.ts | 1 - .../src/blocks/built-in/links.test.tsx | 1 - packages/vitnode/src/blocks/define.test.ts | 1 - .../src/blocks/edit-widgets-host.test.tsx | 52 +-- packages/vitnode/src/blocks/edit.test.tsx | 1 - packages/vitnode/src/blocks/instance.test.ts | 1 - .../vitnode/src/blocks/page-save.test.tsx | 1 - packages/vitnode/src/blocks/page.test.tsx | 1 - packages/vitnode/src/blocks/registry.test.ts | 1 - packages/vitnode/src/blocks/renderer.test.tsx | 1 - packages/vitnode/src/blocks/shape.test.ts | 1 - .../src/blocks/validate-content-field.test.ts | 1 - packages/vitnode/src/blocks/validate.test.ts | 1 - packages/vitnode/src/blocks/variant.test.ts | 1 - packages/vitnode/src/blocks/zone-meta.test.ts | 1 - .../components/ai/ai-field-assist.test.tsx | 1 - .../src/components/form/auto-form.test.tsx | 296 ------------------ .../components/form/fields/combobox.test.tsx | 64 ---- .../form/fields/file-dropzone.test.tsx | 120 ------- .../src/components/form/fields/file.test.tsx | 104 ------ .../src/components/form/fields/files.test.tsx | 178 ----------- .../form/fields/multi-lang.test.tsx | 81 ----- .../form/fields/roles-boundaries.test.ts | 88 ------ .../components/rich-text/boundaries.test.ts | 1 - .../rich-text/rich-text-content.test.tsx | 3 +- .../switchers/themes/theme-switchers.test.tsx | 1 - .../src/components/table/content.test.tsx | 1 - .../src/components/table/expansion.test.tsx | 1 - .../table/order-table-head.test.tsx | 1 - .../src/components/table/reorder.test.tsx | 1 - .../components/table/table-boundaries.test.ts | 65 ---- .../components/tiptap/collaboration.test.tsx | 25 -- .../tiptap/{panel.test.ts => panel.test.tsx} | 1 - ...itor.test.ts => quick-ask-editor.test.tsx} | 1 - .../slash-command/slash-command-list.test.tsx | 1 - ....test.ts => submit-form-shortcut.test.tsx} | 1 - .../toolbar/actions/quick-ask-action.test.tsx | 1 - .../tiptap/toolbar/tiptap-toolbar.test.tsx | 1 - .../src/components/ui/alert-dialog.test.tsx | 47 --- .../vitnode/src/components/ui/button.test.tsx | 1 - .../src/components/ui/carousel.test.tsx | 1 - .../src/components/ui/cascader.test.tsx | 1 - .../vitnode/src/components/ui/chart.test.tsx | 1 - .../src/components/ui/copy-button.test.tsx | 1 - .../src/components/ui/counter.test.tsx | 1 - .../src/components/ui/date-picker.test.tsx | 1 - .../components/ui/date-range-picker.test.tsx | 1 - .../vitnode/src/components/ui/dialog.test.tsx | 1 - .../ui/dynamic-icon.client.test.tsx | 1 - .../src/components/ui/input-group.test.tsx | 60 ---- .../src/components/ui/number-field.test.tsx | 137 -------- .../src/components/ui/pagination.test.tsx | 108 ------- .../src/components/ui/progress.test.tsx | 44 --- .../src/components/ui/sidebar.test.tsx | 1 - .../src/components/ui/spinner.test.tsx | 27 -- .../src/components/ui/toggle-group.test.tsx | 47 --- .../src/components/ui/tooltip.test.tsx | 57 ---- .../src/content/admin/registry.test.ts | 1 - .../vitnode/src/content/admin/route.test.ts | 1 - .../vitnode/src/content/admin/spec.test.ts | 1 - .../vitnode/src/content/admin/upload.test.ts | 1 - packages/vitnode/src/content/cache.test.ts | 1 - .../src/content/editor/boundaries.test.ts | 78 ----- .../src/content/field-constraints.test.ts | 1 - packages/vitnode/src/content/hash.test.ts | 120 ------- .../vitnode/src/content/localization.test.ts | 1 - packages/vitnode/src/content/public.test.ts | 1 - .../src/content/publication-state.test.ts | 1 - .../vitnode/src/content/publication.test.ts | 1 - packages/vitnode/src/content/registry.test.ts | 1 - .../vitnode/src/content/revisions.test.ts | 1 - .../src/content/rich-text-field.test.ts | 1 - .../src/content/rich-text/document.test.ts | 1 - .../src/content/rich-text/text.test.ts | 1 - packages/vitnode/src/content/schemas.test.ts | 1 - .../content/server/delivery-routes.test.ts | 1 - .../src/content/server/delivery-urls.test.ts | 1 - .../content/server/editorial-effects.test.ts | 1 - .../content/server/editorial-service.test.ts | 1 - .../content/server/error-contracts.test.ts | 1 - .../content/server/file-collection.test.ts | 1 - .../server/file-reference-http-errors.test.ts | 1 - .../content/server/file-upload-route.test.ts | 1 - .../vitnode/src/content/server/files.test.ts | 1 - .../src/content/server/http-errors.test.ts | 1 - .../content/server/language-resolver.test.ts | 1 - .../content/server/list-references.test.ts | 1 - .../src/content/server/live-routes.test.ts | 1 - .../src/content/server/live/auth.test.ts | 1 - .../src/content/server/live/documents.test.ts | 1 - .../src/content/server/live/presence.test.ts | 1 - .../server/localized-admin-routes.test.ts | 1 - .../server/localized-base-preview.test.ts | 1 - .../server/localized-list-routes.test.ts | 1 - .../server/localized-preview-routes.test.ts | 1 - .../server/localized-public-routes.test.ts | 1 - .../content/server/localized-search.test.ts | 1 - .../content/server/localized-service.test.ts | 1 - .../vitnode/src/content/server/module.test.ts | 1 - .../content/server/pagination-routes.test.ts | 1 - .../content/server/permission-matrix.test.ts | 1 - .../src/content/server/preview-config.test.ts | 1 - .../src/content/server/preview-link.test.ts | 1 - .../src/content/server/preview-route.test.ts | 1 - .../src/content/server/preview-token.test.ts | 1 - .../src/content/server/public-locales.test.ts | 1 - .../src/content/server/public-privacy.test.ts | 1 - .../src/content/server/public-routes.test.ts | 1 - .../src/content/server/public-service.test.ts | 1 - .../src/content/server/publication.test.ts | 1 - .../vitnode/src/content/server/query.test.ts | 1 - .../content/server/revalidate-bridge.test.ts | 1 - .../content/server/revision-snapshot.test.ts | 1 - .../content/server/revisions-model.test.ts | 1 - .../content/server/schedule-effects.test.ts | 1 - .../content/server/search-document.test.ts | 1 - .../src/content/server/search-indexer.test.ts | 1 - .../server/search-sync.localized.test.ts | 1 - .../src/content/server/search-sync.test.ts | 1 - .../src/content/server/service.test.ts | 1 - .../translation-advanced-revisions.test.ts | 1 - .../translation-editorial-routes.test.ts | 1 - .../translation-editorial-service.test.ts | 1 - .../server/translation-effects.test.ts | 1 - .../content/server/translation-model.test.ts | 1 - .../server/translation-preview-token.test.ts | 1 - .../translation-publication-routes.test.ts | 1 - .../content/server/translation-routes.test.ts | 1 - .../content/translation-editorial.test-d.ts | 1 - .../src/content/translation-schemas.test.ts | 1 - .../src/editor/adapter/refusal.test.ts | 1 - .../src/editor/block-picker/catalog.test.ts | 1 - .../editor/block-shell/block-shell.test.tsx | 1 - .../vitnode/src/editor/boundaries.test.ts | 1 - .../src/editor/dnd/resolve-drop.test.ts | 1 - .../src/editor/properties/area-layout.test.ts | 1 - .../src/editor/properties/selection.test.ts | 1 - .../src/editor/sidebar/messages.test.ts | 75 ----- .../src/editor/sidebar/sidebar.test.tsx | 1 - .../src/editor/state/save-remount.test.ts | 1 - .../src/editor/zones/mount-contract.test.ts | 246 --------------- .../src/editor/zones/open-slots.test.ts | 1 - .../blocks-registry/app-graph.test.ts | 1 - .../blocks-registry/generate.test.ts | 1 - .../core-locale-files.test.ts | 1 - .../package-messages/generate.test.ts | 1 - .../package-messages/resolve.test.ts | 1 - .../framework/plugin-routes/compile.test.ts | 1 - .../plugin-routes/component-source.test.ts | 1 - .../plugin-routes/content-urls.test.ts | 1 - .../src/framework/plugin-routes/core.test.ts | 48 --- .../plugin-routes/diagnostics.test.ts | 1 - .../framework/plugin-routes/generate.test.ts | 1 - .../plugin-routes/host-routes.test.ts | 1 - .../plugin-routes/locale-route-paths.test.ts | 1 - .../framework/plugin-routes/resolve.test.ts | 1 - .../vite/no-materialized-routes.test.ts | 1 - ....ts => use-intersection-observer.test.tsx} | 4 +- .../src/hooks/use-intersection-observer.ts | 3 +- ...age.test.ts => use-local-storage.test.tsx} | 1 - .../vitnode/src/hooks/use-mobile.test.tsx | 1 - .../vitnode/src/lib/ai/usage-tone.test.ts | 20 -- .../vitnode/src/lib/api/signed-token.test.ts | 1 - packages/vitnode/src/lib/api/upload.ts | 5 +- .../vitnode/src/lib/fetcher-client.test.ts | 1 - ...rate-limit.test.ts => rate-limit.test.tsx} | 1 - packages/vitnode/src/lib/fetcher/raw.test.ts | 1 - .../src/lib/i18n/pick-messages.test.ts | 1 - .../lib/i18n/rich-message-call-sites.test.ts | 1 - packages/vitnode/src/lib/plugin.test.ts | 39 --- packages/vitnode/src/lib/query-client.test.ts | 66 ---- ....test.ts => sanitize-editor-html.test.tsx} | 1 - packages/vitnode/src/routes.test.ts | 1 - .../vitnode/src/routing/boundaries.test.ts | 1 - packages/vitnode/src/routing/graph.test.ts | 1 - packages/vitnode/src/routing/manifest.test.ts | 1 - packages/vitnode/src/routing/module.test.ts | 1 - .../vitnode/src/routing/namespaces.test.ts | 1 - packages/vitnode/src/routing/path.test.ts | 1 - packages/vitnode/src/routing/tree.test.ts | 1 - .../tanstack/admin/content/form/route.test.ts | 1 - .../src/tanstack/admin/content/route.test.ts | 1 - .../tanstack/admin/permissions-bridge.test.ts | 188 ----------- .../tanstack/admin/screen-boundaries.test.ts | 1 - ...presence.test.ts => tab-presence.test.tsx} | 9 +- .../tanstack/admin/users/invalidate.test.ts | 1 - .../src/tanstack/breadcrumb/pending.test.tsx | 1 - .../tanstack/content/delivery-page.test.ts | 1 - .../tanstack/i18n/provider-records.test.ts | 69 ---- .../vitnode/src/tanstack/icons/ssr.test.ts | 1 - .../src/tanstack/metadata/metadata.test.ts | 1 - .../src/tanstack/notifications/bell.test.tsx | 1 - .../notifications/state-sync.test.tsx | 8 +- .../tanstack/pending/guard-pending.test.tsx | 1 - .../plugin-routes/components.test.tsx | 1 - ...hness.test.ts => mount-freshness.test.tsx} | 1 - .../tanstack/plugin-routes/translator.test.ts | 1 - .../src/tanstack/profile/route.test.ts | 1 - .../{index.test.ts => index.test.tsx} | 1 - .../vitnode/src/tests/openapi-validate.ts | 160 ---------- packages/vitnode/src/tests/setup-dom.ts | 7 + .../admin/layouts/shell-boundaries.test.ts | 1 - .../layouts/sidebar/nav/item-content.test.tsx | 1 - .../content/actions/conflict-messages.test.ts | 113 ------- .../content/actions/delivery-model.test.ts | 1 - .../content/actions/editorial-query.test.ts | 1 - .../content/actions/row-actions-model.test.ts | 1 - .../admin/views/content/content-query.test.ts | 1 - .../admin/views/content/content-retry.test.ts | 119 ------- .../admin/views/content/form/diff.test.ts | 1 - .../views/content/form/item-query.test.ts | 1 - .../views/content/form/mutations-api.test.ts | 1 - .../views/content/form/status-switch.test.tsx | 1 - .../views/content/lib/api-result.test.ts | 1 - .../views/content/lib/invalidate.test.ts | 1 - .../views/content/lib/options-query.test.ts | 1 - .../live/collaborative-editor.test.tsx | 1 - .../views/content/live/doc-provider.test.ts | 1 - .../admin/views/content/live/field.test.tsx | 10 +- .../views/content/live/presence.test.tsx | 1 - .../admin/views/content/live/status.test.tsx | 1 - .../views/content/live/use-session.test.tsx | 8 +- .../admin/views/content/table/cells.test.tsx | 5 +- .../admin/views/content/table/columns.test.ts | 1 - .../views/content/table/list-query.test.ts | 1 - .../cron/cron-scheduler-status.test.tsx | 1 - .../core/ai/actions/actions-content.test.tsx | 1 - .../ai/overview/overview-content.test.tsx | 1 - .../core/dashboard/grid/widget-panel.test.tsx | 1 - .../navigation-list-content.test.tsx | 1 - .../system/files/alt/file-alt-editor.test.tsx | 1 - .../users/detail/user-account-panels.test.tsx | 9 +- .../users/detail/user-detail-content.test.tsx | 1 - .../form/password-reset-form-content.test.tsx | 37 --- .../settings/ai/ai-usage-content.test.tsx | 1 - .../settings/devices/device-item.test.tsx | 1 - .../views/auth/settings/nav-content.test.tsx | 1 - .../passkeys/passkeys-content.test.tsx | 1 - .../sso/sso-connections-content.test.tsx | 1 - .../sso/buttons/sso-buttons-content.test.tsx | 1 - .../confirm-email-content.test.tsx | 1 - .../src/views/error/error-content.test.tsx | 1 - .../theme/header/header-content.test.tsx | 1 - .../theme/header/header-nav-menu.test.tsx | 1 - .../header/mobile-nav/mobile-nav-bar.test.tsx | 1 - .../mobile-nav/mobile-user-menu.test.tsx | 1 - packages/vitnode/vitest.config.ts | 125 ++++++-- .../src/api/admin-staff-permission.test.ts | 1 - .../modules/admin/ai/ai.admin.module.test.ts | 1 - plugins/blog/src/content/post-urls.test.ts | 88 ------ .../admin/article/editor/ai-review.test.tsx | 1 + plugins/blog/vitest.config.ts | 2 +- .../src/api/admin-staff-permission.test.ts | 1 - pnpm-lock.yaml | 119 ++++--- 342 files changed, 247 insertions(+), 3943 deletions(-) delete mode 100644 packages/create-vitnode-app/src/plugin/validation.test.ts delete mode 100644 packages/vitnode/scripts/no-route-copier.test.ts delete mode 100644 packages/vitnode/src/api/middlewares/global.middleware.test.ts delete mode 100644 packages/vitnode/src/api/modules/admin/files/routes/download-access.test.ts delete mode 100644 packages/vitnode/src/api/modules/admin/users/routes/show.route.test.ts delete mode 100644 packages/vitnode/src/api/modules/cron/cron/clean.cron.test.ts delete mode 100644 packages/vitnode/src/components/form/auto-form.test.tsx delete mode 100644 packages/vitnode/src/components/form/fields/combobox.test.tsx delete mode 100644 packages/vitnode/src/components/form/fields/file-dropzone.test.tsx delete mode 100644 packages/vitnode/src/components/form/fields/file.test.tsx delete mode 100644 packages/vitnode/src/components/form/fields/files.test.tsx delete mode 100644 packages/vitnode/src/components/form/fields/multi-lang.test.tsx delete mode 100644 packages/vitnode/src/components/form/fields/roles-boundaries.test.ts delete mode 100644 packages/vitnode/src/components/table/table-boundaries.test.ts delete mode 100644 packages/vitnode/src/components/tiptap/collaboration.test.tsx rename packages/vitnode/src/components/tiptap/{panel.test.ts => panel.test.tsx} (98%) rename packages/vitnode/src/components/tiptap/quick-ask/{quick-ask-editor.test.ts => quick-ask-editor.test.tsx} (99%) rename packages/vitnode/src/components/tiptap/{submit-form-shortcut.test.ts => submit-form-shortcut.test.tsx} (97%) delete mode 100644 packages/vitnode/src/components/ui/alert-dialog.test.tsx delete mode 100644 packages/vitnode/src/components/ui/input-group.test.tsx delete mode 100644 packages/vitnode/src/components/ui/number-field.test.tsx delete mode 100644 packages/vitnode/src/components/ui/pagination.test.tsx delete mode 100644 packages/vitnode/src/components/ui/progress.test.tsx delete mode 100644 packages/vitnode/src/components/ui/spinner.test.tsx delete mode 100644 packages/vitnode/src/components/ui/toggle-group.test.tsx delete mode 100644 packages/vitnode/src/components/ui/tooltip.test.tsx delete mode 100644 packages/vitnode/src/content/editor/boundaries.test.ts delete mode 100644 packages/vitnode/src/content/hash.test.ts delete mode 100644 packages/vitnode/src/editor/sidebar/messages.test.ts delete mode 100644 packages/vitnode/src/editor/zones/mount-contract.test.ts delete mode 100644 packages/vitnode/src/framework/plugin-routes/core.test.ts rename packages/vitnode/src/hooks/{use-intersection-observer.test.ts => use-intersection-observer.test.tsx} (97%) rename packages/vitnode/src/hooks/{use-local-storage.test.ts => use-local-storage.test.tsx} (99%) delete mode 100644 packages/vitnode/src/lib/ai/usage-tone.test.ts rename packages/vitnode/src/lib/fetcher/{rate-limit.test.ts => rate-limit.test.tsx} (98%) delete mode 100644 packages/vitnode/src/lib/plugin.test.ts delete mode 100644 packages/vitnode/src/lib/query-client.test.ts rename packages/vitnode/src/lib/{sanitize-editor-html.test.ts => sanitize-editor-html.test.tsx} (99%) delete mode 100644 packages/vitnode/src/tanstack/admin/permissions-bridge.test.ts rename packages/vitnode/src/tanstack/admin/{tab-presence.test.ts => tab-presence.test.tsx} (95%) delete mode 100644 packages/vitnode/src/tanstack/i18n/provider-records.test.ts rename packages/vitnode/src/tanstack/plugin-routes/{mount-freshness.test.ts => mount-freshness.test.tsx} (99%) rename packages/vitnode/src/tanstack/view-transitions/{index.test.ts => index.test.tsx} (99%) delete mode 100644 packages/vitnode/src/tests/openapi-validate.ts create mode 100644 packages/vitnode/src/tests/setup-dom.ts delete mode 100644 packages/vitnode/src/views/admin/views/content/actions/conflict-messages.test.ts delete mode 100644 packages/vitnode/src/views/admin/views/content/content-retry.test.ts delete mode 100644 packages/vitnode/src/views/auth/password-reset/form/password-reset-form-content.test.tsx delete mode 100644 plugins/blog/src/content/post-urls.test.ts diff --git a/AGENTS.md b/AGENTS.md index 54bc5df57f..754c454711 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -113,6 +113,15 @@ npm i x - Do not test static config, constants, color presets, or plugin configuration (`config.api`, content type definitions). - Do not test third-party libraries or trivial helper/wrapper code. - Always run "pnpm lint:fix" to fix formatting and linting issues before committing. Never commit a file with lint errors. +- Do not assert on source text (`readFileSync` + `toContain` on a module) - test behaviour. Import-graph checks that keep server or editor code out of a bundle are fine. + +### Speed + +- The file name picks the environment: `*.test.tsx` runs in happy-dom (project `dom`), `*.test.ts` runs in node (project `unit`). Never add a `// @vitest-environment` pragma. +- `unit` shares one module graph per worker (`isolate: false`). A test that asserts process-wide state ("nothing registered yet") goes in `ISOLATED` in `packages/vitnode/vitest.config.ts`. +- Real builds, watchers and child processes go in `INTEGRATION` there. `pnpm test` runs everything; `pnpm test:unit` skips them. +- Never wait out a real delay - use `vi.useFakeTimers()` and advance the clock. +- Build a Hono app or a fixture once per file when the tests only read from it. ### Mocking diff --git a/packages/create-vitnode-app/src/plugin/validation.test.ts b/packages/create-vitnode-app/src/plugin/validation.test.ts deleted file mode 100644 index 2241c68a0d..0000000000 --- a/packages/create-vitnode-app/src/plugin/validation.test.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import { reservedPluginNameProblem } from "./validation.js"; - -describe("reservedPluginNameProblem", () => { - it.each(["admin", "api", "login", "users"])( - "refuses %s, a route core already serves", - name => { - expect(reservedPluginNameProblem(name)).toContain(`/${name}`); - }, - ); - - it.each(["blog", "forum", "admin-tools"])("accepts %s", name => { - expect(reservedPluginNameProblem(name)).toBeNull(); - }); -}); diff --git a/packages/vitnode/package.json b/packages/vitnode/package.json index 3b9ee5f49a..597470d687 100644 --- a/packages/vitnode/package.json +++ b/packages/vitnode/package.json @@ -66,6 +66,7 @@ "@vitnode/config": "workspace:*", "drizzle-kit": "1.0.0-rc.4", "drizzle-orm": "1.0.0-rc.4", + "happy-dom": "^20.14.6", "hono": "^4.13.13", "jiti": "^2.7.0", "jsdom": "^30.1.2", @@ -219,10 +220,11 @@ "build:plugins": "node dist/scripts/scripts.js build", "dev": "node dist/scripts/scripts.js dev", "dev:email": "email dev --dir src/emails", - "test": "vitest run", - "test:watch": "vitest", - "test:coverage": "vitest run --coverage", - "test:types": "vitest run --typecheck.only", + "test": "vitest run --project unit --project isolated --project dom --project integration", + "test:unit": "vitest run --project unit --project isolated --project dom", + "test:watch": "vitest --project unit --project isolated --project dom", + "test:coverage": "vitest run --project unit --project isolated --project dom --project integration --coverage", + "test:types": "vitest run --project types", "lint": "oxlint --type-aware", "lint:fix": "oxlint --type-aware --fix" }, diff --git a/packages/vitnode/scripts/cli/builder/analysis.test.ts b/packages/vitnode/scripts/cli/builder/analysis.test.ts index f02d293901..5539e252b3 100644 --- a/packages/vitnode/scripts/cli/builder/analysis.test.ts +++ b/packages/vitnode/scripts/cli/builder/analysis.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; diff --git a/packages/vitnode/scripts/cli/builder/measure.test.ts b/packages/vitnode/scripts/cli/builder/measure.test.ts index 4d7b9bb1b8..07860db744 100644 --- a/packages/vitnode/scripts/cli/builder/measure.test.ts +++ b/packages/vitnode/scripts/cli/builder/measure.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; diff --git a/packages/vitnode/scripts/cli/builder/output-files.test.ts b/packages/vitnode/scripts/cli/builder/output-files.test.ts index 9809ca5d96..f5e2070e3b 100644 --- a/packages/vitnode/scripts/cli/builder/output-files.test.ts +++ b/packages/vitnode/scripts/cli/builder/output-files.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import { describe, expect, it } from "vitest"; import { diff --git a/packages/vitnode/scripts/cli/builder/package-build.test.ts b/packages/vitnode/scripts/cli/builder/package-build.test.ts index 552eb00a9d..c372c0ace2 100644 --- a/packages/vitnode/scripts/cli/builder/package-build.test.ts +++ b/packages/vitnode/scripts/cli/builder/package-build.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import { execFileSync } from "node:child_process"; import { cpSync, @@ -14,7 +13,15 @@ import { } from "node:fs"; import { tmpdir } from "node:os"; import { dirname, join, relative } from "node:path"; -import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { + afterAll, + afterEach, + beforeAll, + beforeEach, + describe, + expect, + it, +} from "vitest"; import { runBuildCommand } from "../commands/build"; import { RuntimeError } from "../errors"; @@ -117,7 +124,7 @@ const fixture = () => { write("src/__fixtures__/sample.json", "{}\n"); }; -beforeEach(() => { +const createPackage = () => { root = mkdtempSync(join(tmpdir(), "vitnode-package-build-")); // The package's own node_modules, as an install would leave it: tsdown, // TypeScript and React's types resolve from there. The sources themselves @@ -129,11 +136,11 @@ beforeEach(() => { "junction", ); fixture(); -}); +}; -afterEach(() => { +const removePackage = () => { rmSync(root, { force: true, recursive: true }); -}); +}; const build = async () => { const { context, runtime } = createTestContext({ cwd: root }); @@ -173,8 +180,18 @@ const importWithNode = (expression: string) => ).trim(); describe("vitnode build in a plugin package", () => { - it("compiles every module under src into dist/src with tsdown and checks types", async () => { - const { code, output } = await build(); + // One build serves every assertion that only reads its output. + let result: Awaited>; + + beforeAll(async () => { + createPackage(); + result = await build(); + }); + + afterAll(removePackage); + + it("compiles every module under src into dist/src with tsdown and checks types", () => { + const { code, output } = result; expect(code).toBe(0); expect(output).toContain("✓ Compiling sources"); @@ -193,11 +210,9 @@ describe("vitnode build in a plugin package", () => { // Every relative import - JavaScript and declarations - names a file // that was written, as Node's ESM resolution requires. expect(unresolvedRelativeImports(join(root, "dist", "src"))).toEqual([]); - }, 60_000); - - it("emits ESM that Node runs, with aliases, JSX, dynamic imports and side effects intact", async () => { - await build(); + }); + it("emits ESM that Node runs, with aliases, JSX, dynamic imports and side effects intact", () => { expect(read("dist/src/components/greeting.js")).toContain( "react/jsx-runtime", ); @@ -221,11 +236,9 @@ describe("vitnode build in a plugin package", () => { 'const { quadruple } = await import("./dist/src/uses-alias.js"); const { loadPage } = await import("./dist/src/lazy.js"); await import("./dist/src/register.js"); const { helloModule } = await import("./dist/src/api/hello.module.js"); console.log(quadruple(2), (await loadPage()).default, globalThis.fixtureLoaded, helloModule.routes[0]);', ), ).toBe("8 page true /hello"); - }, 60_000); - - it("copies JSON next to the module that imports it, with the import attribute kept", async () => { - await build(); + }); + it("copies JSON next to the module that imports it, with the import attribute kept", () => { expect(read("dist/src/locales/en.json")).toBe(read("src/locales/en.json")); expect(read("dist/src/locales/index.js")).toMatch( /import\(\s*["'`]\.\/en\.json["'`]\s*,\s*\{\s*with:\s*\{\s*type:\s*["'`]json["'`]/, @@ -235,11 +248,9 @@ describe("vitnode build in a plugin package", () => { 'const { default: messages } = await import("./dist/src/locales/index.js"); console.log((await messages.en()).default.fixture.hello);', ), ).toBe("Hi"); - }, 60_000); - - it("writes declarations with relative imports, declaration maps and the ambient types the package declares", async () => { - await build(); + }); + it("writes declarations with relative imports, declaration maps and the ambient types the package declares", () => { const declaration = read("dist/src/uses-alias.d.ts"); expect(declaration).toMatch(/from\s*["']\.\/lib\/math\.js["']/); expect(declaration).not.toContain("@/"); @@ -255,14 +266,18 @@ describe("vitnode build in a plugin package", () => { expect(read("dist/src/registry.d.ts")).toMatch( /readGreeting: \(\) => "hello"/, ); - }, 60_000); - - it("ships no JavaScript source maps from a production build", async () => { - await build(); + }); + it("ships no JavaScript source maps from a production build", () => { expect(exists("dist/src/standalone.js.map")).toBe(false); expect(read("dist/src/standalone.js")).not.toContain("sourceMappingURL"); - }, 60_000); + }); +}); + +describe("vitnode build on a package that changes", () => { + beforeEach(createPackage); + + afterEach(removePackage); it("fails on a type error, as tsc always made it", async () => { write("src/broken.ts", 'export const value: number = "text";\n'); diff --git a/packages/vitnode/scripts/cli/builder/scripts-build.test.ts b/packages/vitnode/scripts/cli/builder/scripts-build.test.ts index a5b5bc0f2e..d8c84fe3a6 100644 --- a/packages/vitnode/scripts/cli/builder/scripts-build.test.ts +++ b/packages/vitnode/scripts/cli/builder/scripts-build.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import { execFileSync, spawnSync } from "node:child_process"; import { existsSync, diff --git a/packages/vitnode/scripts/cli/builder/size-severity.test.ts b/packages/vitnode/scripts/cli/builder/size-severity.test.ts index 6b8bd26224..93cafa7852 100644 --- a/packages/vitnode/scripts/cli/builder/size-severity.test.ts +++ b/packages/vitnode/scripts/cli/builder/size-severity.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import { describe, expect, it } from "vitest"; import { classifySize, needsWarning } from "./size-severity"; diff --git a/packages/vitnode/scripts/cli/builder/snapshot.test.ts b/packages/vitnode/scripts/cli/builder/snapshot.test.ts index c666faaf34..9ed8c24e94 100644 --- a/packages/vitnode/scripts/cli/builder/snapshot.test.ts +++ b/packages/vitnode/scripts/cli/builder/snapshot.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { dirname, join } from "node:path"; diff --git a/packages/vitnode/scripts/cli/commands/build.test.ts b/packages/vitnode/scripts/cli/commands/build.test.ts index 84d2685c2b..4438805eda 100644 --- a/packages/vitnode/scripts/cli/commands/build.test.ts +++ b/packages/vitnode/scripts/cli/commands/build.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import type { InlineConfig, Plugin } from "vite"; import { diff --git a/packages/vitnode/scripts/cli/commands/db.test.ts b/packages/vitnode/scripts/cli/commands/db.test.ts index 9ef32c2e47..246fb57760 100644 --- a/packages/vitnode/scripts/cli/commands/db.test.ts +++ b/packages/vitnode/scripts/cli/commands/db.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; diff --git a/packages/vitnode/scripts/cli/commands/dev.test.ts b/packages/vitnode/scripts/cli/commands/dev.test.ts index e295565120..f18f5a8b8e 100644 --- a/packages/vitnode/scripts/cli/commands/dev.test.ts +++ b/packages/vitnode/scripts/cli/commands/dev.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import type { ViteDevServer } from "vite"; import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; diff --git a/packages/vitnode/scripts/cli/commands/i18n.test.ts b/packages/vitnode/scripts/cli/commands/i18n.test.ts index 26a5e52c9e..5e2c9d3cad 100644 --- a/packages/vitnode/scripts/cli/commands/i18n.test.ts +++ b/packages/vitnode/scripts/cli/commands/i18n.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import { existsSync, mkdirSync, diff --git a/packages/vitnode/scripts/cli/commands/plugin.test.ts b/packages/vitnode/scripts/cli/commands/plugin.test.ts index ac607b3c30..109f186363 100644 --- a/packages/vitnode/scripts/cli/commands/plugin.test.ts +++ b/packages/vitnode/scripts/cli/commands/plugin.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import { createJiti } from "jiti"; import { cpSync, diff --git a/packages/vitnode/scripts/cli/commands/start.test.ts b/packages/vitnode/scripts/cli/commands/start.test.ts index f005289b28..fd4b70ce99 100644 --- a/packages/vitnode/scripts/cli/commands/start.test.ts +++ b/packages/vitnode/scripts/cli/commands/start.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { createServer } from "node:net"; import { tmpdir } from "node:os"; diff --git a/packages/vitnode/scripts/cli/db/db.test.ts b/packages/vitnode/scripts/cli/db/db.test.ts index 27af970793..a6270a7dbd 100644 --- a/packages/vitnode/scripts/cli/db/db.test.ts +++ b/packages/vitnode/scripts/cli/db/db.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import { describe, expect, it } from "vitest"; import type { AppliedMigration, LocalMigration } from "./migration-state"; diff --git a/packages/vitnode/scripts/cli/dev/package-watch.test.ts b/packages/vitnode/scripts/cli/dev/package-watch.test.ts index c32de411f5..9dbd509bfa 100644 --- a/packages/vitnode/scripts/cli/dev/package-watch.test.ts +++ b/packages/vitnode/scripts/cli/dev/package-watch.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import type { ChildProcess } from "node:child_process"; import { diff --git a/packages/vitnode/scripts/cli/index.test.ts b/packages/vitnode/scripts/cli/index.test.ts index fae03945f6..8c3a74b7c5 100644 --- a/packages/vitnode/scripts/cli/index.test.ts +++ b/packages/vitnode/scripts/cli/index.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import { describe, expect, it } from "vitest"; import { PUBLIC_COMMANDS } from "./help"; diff --git a/packages/vitnode/scripts/cli/plugins/workspace.test.ts b/packages/vitnode/scripts/cli/plugins/workspace.test.ts index b6f2ce5a84..db9936da48 100644 --- a/packages/vitnode/scripts/cli/plugins/workspace.test.ts +++ b/packages/vitnode/scripts/cli/plugins/workspace.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; diff --git a/packages/vitnode/scripts/cli/project/package-manager.test.ts b/packages/vitnode/scripts/cli/project/package-manager.test.ts index 0712a7fd6e..5170327f4c 100644 --- a/packages/vitnode/scripts/cli/project/package-manager.test.ts +++ b/packages/vitnode/scripts/cli/project/package-manager.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; diff --git a/packages/vitnode/scripts/cli/project/runtime.test.ts b/packages/vitnode/scripts/cli/project/runtime.test.ts index dd1e2ded06..3c50ec4c1c 100644 --- a/packages/vitnode/scripts/cli/project/runtime.test.ts +++ b/packages/vitnode/scripts/cli/project/runtime.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; diff --git a/packages/vitnode/scripts/cli/ui/format.test.ts b/packages/vitnode/scripts/cli/ui/format.test.ts index 4fc841890f..2b170e22f3 100644 --- a/packages/vitnode/scripts/cli/ui/format.test.ts +++ b/packages/vitnode/scripts/cli/ui/format.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import { describe, expect, it } from "vitest"; import { diff --git a/packages/vitnode/scripts/cli/ui/terminal.test.ts b/packages/vitnode/scripts/cli/ui/terminal.test.ts index 8b7108a0fb..55b079c70a 100644 --- a/packages/vitnode/scripts/cli/ui/terminal.test.ts +++ b/packages/vitnode/scripts/cli/ui/terminal.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import { describe, expect, it } from "vitest"; import { detectTerminal, isCi, isUnicodeSupported } from "./terminal"; diff --git a/packages/vitnode/scripts/cli/ui/ui.test.ts b/packages/vitnode/scripts/cli/ui/ui.test.ts index 6c3f1164a1..dcb77835df 100644 --- a/packages/vitnode/scripts/cli/ui/ui.test.ts +++ b/packages/vitnode/scripts/cli/ui/ui.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import { describe, expect, it, vi } from "vitest"; import { createFakeRuntime } from "../testing"; diff --git a/packages/vitnode/scripts/database-bootstrap.test.ts b/packages/vitnode/scripts/database-bootstrap.test.ts index 25248fc43d..a6a19eedfa 100644 --- a/packages/vitnode/scripts/database-bootstrap.test.ts +++ b/packages/vitnode/scripts/database-bootstrap.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import { existsSync, mkdtempSync, diff --git a/packages/vitnode/scripts/i18n-check.test.ts b/packages/vitnode/scripts/i18n-check.test.ts index e4efdfc344..f72120de64 100644 --- a/packages/vitnode/scripts/i18n-check.test.ts +++ b/packages/vitnode/scripts/i18n-check.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; diff --git a/packages/vitnode/scripts/no-route-copier.test.ts b/packages/vitnode/scripts/no-route-copier.test.ts deleted file mode 100644 index 7d5dd182b8..0000000000 --- a/packages/vitnode/scripts/no-route-copier.test.ts +++ /dev/null @@ -1,137 +0,0 @@ -// @vitest-environment node -import { existsSync, readdirSync, readFileSync, statSync } from "node:fs"; -import { join, relative, resolve } from "node:path"; -import { describe, expect, it } from "vitest"; - -const scriptsRoot = resolve(import.meta.dirname); -const packageRoot = resolve(scriptsRoot, ".."); - -const SKIP = new Set([".git", "dist", "node_modules"]); - -const filesUnder = (directory: string): string[] => { - if (!existsSync(directory)) return []; - - const walk = (current: string): string[] => - readdirSync(current).flatMap(name => { - if (SKIP.has(name)) return []; - - const path = join(current, name); - - return statSync(path).isDirectory() - ? walk(path) - : [relative(packageRoot, path).replaceAll("\\", "/")]; - }); - - return walk(directory).sort(); -}; - -const codeOf = (file: string): string => - readFileSync(join(packageRoot, file), "utf8") - .replace(/\/\*[\s\S]*?\*\//g, "") - .replace(/\/\/.*$/gm, ""); - -const scriptFiles = filesUnder(scriptsRoot).filter( - file => file.endsWith(".ts") && !file.endsWith(".test.ts"), -); - -describe("the plugin route copier", () => { - it("has no module left in scripts/", () => { - for (const deleted of [ - "scripts/plugin.ts", - "scripts/prepare-plugins-files.ts", - "scripts/legacy-route-overlap.ts", - ]) { - expect(existsSync(join(packageRoot, deleted))).toBe(false); - } - }); - - it("has no CLI command", () => { - const cli = codeOf("scripts/scripts.ts"); - - expect(cli).not.toContain("prepare-plugins"); - expect(cli).not.toContain('case "plugin"'); - expect(cli).not.toContain("processPlugin"); - expect(cli).not.toContain("preparePluginsFiles"); - }); - - /** - * `vitnode dev` is the other way in - it started the watcher alongside the - * three compilers - and `vitnode init` is where a fresh project ran the copy. - */ - it("is not started by `vitnode dev` or `vitnode init`", () => { - expect(codeOf("scripts/cli/commands/dev.ts")).not.toContain( - "processPlugin", - ); - expect(codeOf("scripts/cli/dev/watchers.ts")).not.toContain( - "processPlugin", - ); - expect(codeOf("scripts/prepare-database.ts")).not.toContain( - "preparePluginsFiles", - ); - }); - - it("leaves no file-copying machinery behind", () => { - const utils = codeOf("scripts/shared/file-utils.ts"); - - for (const gone of [ - "copyDirectoryRecursive", - "cleanupDeletedFiles", - "transformFileImports", - "buildInitialRouteMap", - "findLocaleRoot", - "SourceConfig", - ]) { - expect(utils).not.toContain(gone); - } - - expect(utils).toContain("findRepoRoot"); - expect(utils).toContain("findPackagePath"); - }); - - it("recognises no legacy route directory", () => { - const offenders = scriptFiles.filter(file => { - const code = codeOf(file); - - return [ - '"routes", "main"', - '"routes", "admin"', - '"routes", "blank"', - '"routes", "breadcrumb"', - "LEGACY_ROUTE_DIRECTORIES", - ].some(token => code.includes(token)); - }); - - expect(offenders).toEqual([]); - }); -}); - -describe("the build-time strangler", () => { - it("keeps no list of routes another application owns", () => { - expect( - existsSync( - join(packageRoot, "src/framework/plugin-routes/legacy-routes.ts"), - ), - ).toBe(false); - - const offenders = filesUnder(join(packageRoot, "src")) - .filter(file => /\.tsx?$/.test(file) && !/\.test(-d)?\.tsx?$/.test(file)) - .filter(file => { - const code = codeOf(file); - - return ( - code.includes("assertNoLegacyRouteCollision") || - code.includes("legacyAdminRoutePathsFromFiles") || - code.includes("LegacyRoutePath") || - code.includes("legacyRoutes") - ); - }); - - expect(offenders).toEqual([]); - }); - - it("still refuses a plugin route that shadows the host's own page", () => { - const compiler = codeOf("src/framework/plugin-routes/compile.ts"); - - expect(compiler).toContain("assertNoHostRouteCollision"); - }); -}); diff --git a/packages/vitnode/scripts/write-plugin-api-registry.test.ts b/packages/vitnode/scripts/write-plugin-api-registry.test.ts index 6be160b09c..1f4ee9fa8e 100644 --- a/packages/vitnode/scripts/write-plugin-api-registry.test.ts +++ b/packages/vitnode/scripts/write-plugin-api-registry.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import { mkdirSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; diff --git a/packages/vitnode/src/api/admin-staff-permission.test.ts b/packages/vitnode/src/api/admin-staff-permission.test.ts index 1de1fd4df3..7c0bcb04f1 100644 --- a/packages/vitnode/src/api/admin-staff-permission.test.ts +++ b/packages/vitnode/src/api/admin-staff-permission.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import { OpenAPIHono, z } from "@hono/zod-openapi"; import { describe, expect, it } from "vitest"; diff --git a/packages/vitnode/src/api/lib/ai/accounting.test.ts b/packages/vitnode/src/api/lib/ai/accounting.test.ts index 357b3d0878..3bd1690bfc 100644 --- a/packages/vitnode/src/api/lib/ai/accounting.test.ts +++ b/packages/vitnode/src/api/lib/ai/accounting.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import type { LanguageModelUsage } from "ai"; import { describe, expect, it } from "vitest"; diff --git a/packages/vitnode/src/api/lib/ai/registry.test.ts b/packages/vitnode/src/api/lib/ai/registry.test.ts index 8d31365634..2361769f2b 100644 --- a/packages/vitnode/src/api/lib/ai/registry.test.ts +++ b/packages/vitnode/src/api/lib/ai/registry.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import { describe, expect, it } from "vitest"; import { z } from "zod"; diff --git a/packages/vitnode/src/api/lib/ai/runner.test.ts b/packages/vitnode/src/api/lib/ai/runner.test.ts index 7fa309b8fc..1e9332a3a2 100644 --- a/packages/vitnode/src/api/lib/ai/runner.test.ts +++ b/packages/vitnode/src/api/lib/ai/runner.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import type { Context } from "hono"; import { APICallError } from "ai"; diff --git a/packages/vitnode/src/api/lib/auth-cookie.test.ts b/packages/vitnode/src/api/lib/auth-cookie.test.ts index fe60720f3d..718e629dfb 100644 --- a/packages/vitnode/src/api/lib/auth-cookie.test.ts +++ b/packages/vitnode/src/api/lib/auth-cookie.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import type { Context } from "hono"; import { Hono } from "hono"; diff --git a/packages/vitnode/src/api/lib/content-modules.integration.test.ts b/packages/vitnode/src/api/lib/content-modules.integration.test.ts index 22ee4d3b82..89546eeae1 100644 --- a/packages/vitnode/src/api/lib/content-modules.integration.test.ts +++ b/packages/vitnode/src/api/lib/content-modules.integration.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; diff --git a/packages/vitnode/src/api/lib/error-details.test.ts b/packages/vitnode/src/api/lib/error-details.test.ts index 6b50129e17..cbbb52bbbd 100644 --- a/packages/vitnode/src/api/lib/error-details.test.ts +++ b/packages/vitnode/src/api/lib/error-details.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import { DrizzleQueryError } from "drizzle-orm"; import { describe, expect, it } from "vitest"; diff --git a/packages/vitnode/src/api/lib/file-download.test.ts b/packages/vitnode/src/api/lib/file-download.test.ts index 442b03f844..a4851aec37 100644 --- a/packages/vitnode/src/api/lib/file-download.test.ts +++ b/packages/vitnode/src/api/lib/file-download.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import type { Context } from "hono"; import { beforeEach, describe, expect, it, vi } from "vitest"; diff --git a/packages/vitnode/src/api/lib/image-signature.test.ts b/packages/vitnode/src/api/lib/image-signature.test.ts index f79bb3390e..dc45d840e1 100644 --- a/packages/vitnode/src/api/lib/image-signature.test.ts +++ b/packages/vitnode/src/api/lib/image-signature.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import sharp from "sharp"; import { describe, expect, it } from "vitest"; diff --git a/packages/vitnode/src/api/lib/navigation-presets.test.ts b/packages/vitnode/src/api/lib/navigation-presets.test.ts index ffe16f77ff..2c48ab4c30 100644 --- a/packages/vitnode/src/api/lib/navigation-presets.test.ts +++ b/packages/vitnode/src/api/lib/navigation-presets.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import { describe, expect, it } from "vitest"; import { diff --git a/packages/vitnode/src/api/lib/openapi-tags.test.ts b/packages/vitnode/src/api/lib/openapi-tags.test.ts index 9a157491ad..9afab95366 100644 --- a/packages/vitnode/src/api/lib/openapi-tags.test.ts +++ b/packages/vitnode/src/api/lib/openapi-tags.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import { OpenAPIHono, z } from "@hono/zod-openapi"; import { describe, expect, it } from "vitest"; diff --git a/packages/vitnode/src/api/lib/pagination-cursor.test.ts b/packages/vitnode/src/api/lib/pagination-cursor.test.ts index c58a098aee..d8a9be01ac 100644 --- a/packages/vitnode/src/api/lib/pagination-cursor.test.ts +++ b/packages/vitnode/src/api/lib/pagination-cursor.test.ts @@ -7,7 +7,6 @@ import { timestamp, uuid, } from "drizzle-orm/pg-core"; -// @vitest-environment node import { HTTPException } from "hono/http-exception"; import { describe, expect, it } from "vitest"; diff --git a/packages/vitnode/src/api/lib/plugin.test.ts b/packages/vitnode/src/api/lib/plugin.test.ts index 0b1e591924..379de31d82 100644 --- a/packages/vitnode/src/api/lib/plugin.test.ts +++ b/packages/vitnode/src/api/lib/plugin.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import { describe, expect, it } from "vitest"; import { createContentModel } from "@/content/server/model"; diff --git a/packages/vitnode/src/api/lib/remote-image.test.ts b/packages/vitnode/src/api/lib/remote-image.test.ts index f62e8dc344..92be69a9d2 100644 --- a/packages/vitnode/src/api/lib/remote-image.test.ts +++ b/packages/vitnode/src/api/lib/remote-image.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import { describe, expect, it, vi } from "vitest"; import type { RemoteImageResponse, RemoteImageTransport } from "./remote-image"; diff --git a/packages/vitnode/src/api/lib/response-cache-headers.test.ts b/packages/vitnode/src/api/lib/response-cache-headers.test.ts index d72e19cbc7..1a771b7a99 100644 --- a/packages/vitnode/src/api/lib/response-cache-headers.test.ts +++ b/packages/vitnode/src/api/lib/response-cache-headers.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import { existsSync, readdirSync, readFileSync, statSync } from "node:fs"; import { dirname, join, relative, resolve, sep } from "node:path"; import { fileURLToPath } from "node:url"; diff --git a/packages/vitnode/src/api/lib/sso-avatar-import.test.ts b/packages/vitnode/src/api/lib/sso-avatar-import.test.ts index f067289402..72fa6523f9 100644 --- a/packages/vitnode/src/api/lib/sso-avatar-import.test.ts +++ b/packages/vitnode/src/api/lib/sso-avatar-import.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import type { Context } from "hono"; import { describe, expect, it, vi } from "vitest"; diff --git a/packages/vitnode/src/api/lib/staff-permission-cache.test.ts b/packages/vitnode/src/api/lib/staff-permission-cache.test.ts index f83ca2d761..816789aed4 100644 --- a/packages/vitnode/src/api/lib/staff-permission-cache.test.ts +++ b/packages/vitnode/src/api/lib/staff-permission-cache.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import type { Context } from "hono"; import { beforeEach, describe, expect, it } from "vitest"; diff --git a/packages/vitnode/src/api/lib/with-pagination.test.ts b/packages/vitnode/src/api/lib/with-pagination.test.ts index 5daed76776..3267258014 100644 --- a/packages/vitnode/src/api/lib/with-pagination.test.ts +++ b/packages/vitnode/src/api/lib/with-pagination.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import type { SQL } from "drizzle-orm"; import type { Context } from "hono"; diff --git a/packages/vitnode/src/api/middlewares/global.middleware.test.ts b/packages/vitnode/src/api/middlewares/global.middleware.test.ts deleted file mode 100644 index 688ac0759d..0000000000 --- a/packages/vitnode/src/api/middlewares/global.middleware.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import type { PermissionStaffModulesInput } from "@/api/lib/permission-staff"; -import type { VitNodeApiConfig } from "@/vitnode.config"; - -import { buildApiPlugin } from "@/api/lib/plugin"; -import { defineEditablePage } from "@/content/editor/define"; - -import { globalMiddleware } from "./global.middleware"; - -const settingsPage = defineEditablePage({ - id: "example:settings", - permission: { module: "widgets", permission: "can_edit" }, - zones: { sidebar: { allowed: ["core:text"] } }, -}); - -const boot = (moderator?: PermissionStaffModulesInput) => - globalMiddleware({ - cacheClient: null, - dbProvider: {} as VitNodeApiConfig["dbProvider"], - metadata: { title: "VitNode" }, - plugins: [ - buildApiPlugin({ - pluginId: "@vitnode/example", - editablePages: [settingsPage], - permissionStaff: moderator === undefined ? undefined : { moderator }, - }), - ], - }); - -describe("an editable page's permission", () => { - it("boots when the plugin declares it as a moderator permission", () => { - expect(() => boot({ widgets: ["can_edit"] })).not.toThrow(); - }); - - it("fails boot when the plugin declares no permissions at all", () => { - expect(() => boot()).toThrow(/permission nobody holds/); - }); - - it("fails boot when the module is declared without that permission", () => { - expect(() => boot({ widgets: ["can_view"] })).toThrow(/never declares/); - }); -}); diff --git a/packages/vitnode/src/api/models/device.test.ts b/packages/vitnode/src/api/models/device.test.ts index b7b2e111e3..69e623a8a3 100644 --- a/packages/vitnode/src/api/models/device.test.ts +++ b/packages/vitnode/src/api/models/device.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import type { Context } from "hono"; import { Hono } from "hono"; diff --git a/packages/vitnode/src/api/models/events.test.ts b/packages/vitnode/src/api/models/events.test.ts index 95cbee53e5..a62efd55e3 100644 --- a/packages/vitnode/src/api/models/events.test.ts +++ b/packages/vitnode/src/api/models/events.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import type { Context } from "hono"; import { describe, expect, it, vi } from "vitest"; diff --git a/packages/vitnode/src/api/models/queue.test.ts b/packages/vitnode/src/api/models/queue.test.ts index dcc2fb1fa7..c20c317728 100644 --- a/packages/vitnode/src/api/models/queue.test.ts +++ b/packages/vitnode/src/api/models/queue.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import type { Context } from "hono"; import { describe, expect, it, vi } from "vitest"; diff --git a/packages/vitnode/src/api/models/search.test.ts b/packages/vitnode/src/api/models/search.test.ts index e152f08d9c..d02e558bb7 100644 --- a/packages/vitnode/src/api/models/search.test.ts +++ b/packages/vitnode/src/api/models/search.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import type { Context } from "hono"; import { describe, expect, it, vi } from "vitest"; diff --git a/packages/vitnode/src/api/models/session-admin.test.ts b/packages/vitnode/src/api/models/session-admin.test.ts index 234d0bea1e..4a1b8a3bbf 100644 --- a/packages/vitnode/src/api/models/session-admin.test.ts +++ b/packages/vitnode/src/api/models/session-admin.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import type { Table } from "drizzle-orm"; import { Hono } from "hono"; diff --git a/packages/vitnode/src/api/models/session-revoke.test.ts b/packages/vitnode/src/api/models/session-revoke.test.ts index 06a1d3a8f8..87e207df87 100644 --- a/packages/vitnode/src/api/models/session-revoke.test.ts +++ b/packages/vitnode/src/api/models/session-revoke.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import { describe, expect, it } from "vitest"; import { ForgotPasswordTokenModel } from "@/api/models/password"; diff --git a/packages/vitnode/src/api/models/sso-link-token.test.ts b/packages/vitnode/src/api/models/sso-link-token.test.ts index f5a4f8356e..71ffed8a3c 100644 --- a/packages/vitnode/src/api/models/sso-link-token.test.ts +++ b/packages/vitnode/src/api/models/sso-link-token.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import { describe, expect, it } from "vitest"; import { diff --git a/packages/vitnode/src/api/models/storage-image.test.ts b/packages/vitnode/src/api/models/storage-image.test.ts index 29539a5a05..c78d9fa2fc 100644 --- a/packages/vitnode/src/api/models/storage-image.test.ts +++ b/packages/vitnode/src/api/models/storage-image.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node // Runs in the Node environment so sharp and real image buffers behave as they do // on the server. import type { Context } from "hono"; diff --git a/packages/vitnode/src/api/modules/admin/debug/routes/search-permissions.route.test.ts b/packages/vitnode/src/api/modules/admin/debug/routes/search-permissions.route.test.ts index b27bea39e6..bda9b8ebf6 100644 --- a/packages/vitnode/src/api/modules/admin/debug/routes/search-permissions.route.test.ts +++ b/packages/vitnode/src/api/modules/admin/debug/routes/search-permissions.route.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import type { Context } from "hono"; import { OpenAPIHono } from "@hono/zod-openapi"; diff --git a/packages/vitnode/src/api/modules/admin/debug/routes/search-status.route.test.ts b/packages/vitnode/src/api/modules/admin/debug/routes/search-status.route.test.ts index 58b85e9aef..1fd5b0e1ca 100644 --- a/packages/vitnode/src/api/modules/admin/debug/routes/search-status.route.test.ts +++ b/packages/vitnode/src/api/modules/admin/debug/routes/search-status.route.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import { describe, expect, it } from "vitest"; import type { SearchIndexerConfig } from "@/api/models/search"; diff --git a/packages/vitnode/src/api/modules/admin/files/routes/download-access.test.ts b/packages/vitnode/src/api/modules/admin/files/routes/download-access.test.ts deleted file mode 100644 index 2c94dbe9ca..0000000000 --- a/packages/vitnode/src/api/modules/admin/files/routes/download-access.test.ts +++ /dev/null @@ -1,89 +0,0 @@ -// @vitest-environment node -import { readFileSync } from "node:fs"; -import { dirname, join } from "node:path"; -import { fileURLToPath } from "node:url"; -import { describe, expect, it } from "vitest"; - -import { downloadUserFileRoute } from "../../../users/files/routes/download.route"; -import { downloadFileAdminRoute } from "./download.route"; - -const here = dirname(fileURLToPath(import.meta.url)); - -const USER_ROUTE = join(here, "../../../users/files/routes/download.route.ts"); -const ADMIN_ROUTE = join(here, "download.route.ts"); - -const sourceOf = (path: string): string => readFileSync(path, "utf8"); - -/** - * Who may download which file is the one thing the shared streaming helper is - * not allowed to have absorbed. Each route still asks its own question. - */ -describe("the download routes' access semantics", () => { - it("scopes the user's own download to the rows they own", () => { - expect(sourceOf(USER_ROUTE)).toContain("eq(core_files.userId, user.id)"); - }); - - it("refuses the user's download without a session", () => { - expect(sourceOf(USER_ROUTE)).toContain("new HTTPException(401"); - expect(downloadUserFileRoute.route.responses).toHaveProperty("401"); - }); - - it("lets an admin reach any row, behind a staff permission", () => { - const source = sourceOf(ADMIN_ROUTE); - - expect(source).toContain("eq(core_files.id, fileId)"); - expect(source).not.toContain("core_files.userId"); - expect(source).toContain( - 'adminStaffPermission: { module: "files", permission: "can_download" }', - ); - }); - - it("keeps the admin route out of the anonymous case entirely", () => { - // No `401` to declare: the staff-permission middleware answers first. - expect(downloadFileAdminRoute.route.responses).not.toHaveProperty("401"); - }); -}); - -describe("the 404 both routes answer", () => { - it.each([ - ["the user's own", downloadUserFileRoute], - ["the admin's", downloadFileAdminRoute], - ])("is still %s `{ error }` body", (_name, built) => { - const notFound = built.route.responses[404]; - - expect(notFound.description).toBe("File not found"); - expect( - notFound.content["application/json"].schema.parse({ - error: "File not found", - }), - ).toEqual({ error: "File not found" }); - }); - - it.each([ - ["the user's own", USER_ROUTE], - ["the admin's", ADMIN_ROUTE], - ])("is what %s route falls back to when nothing streamed", (_name, path) => { - const source = sourceOf(path); - - expect(source).toContain("(await attachStoredFile(c, file)) ??"); - expect(source).toContain('c.json({ error: "File not found" }, 404)'); - }); -}); - -describe("the streaming itself", () => { - it.each([ - ["the user's own", USER_ROUTE], - ["the admin's", ADMIN_ROUTE], - ])("is the shared helper in %s route, not a copy", (_name, path) => { - const source = sourceOf(path); - - expect(source).toContain( - 'import { attachStoredFile } from "@/api/lib/file-download";', - ); - // The duplication this removed: the fetch, the two headers and the - // re-streamed body, written out twice. - expect(source).not.toContain("Content-Disposition"); - expect(source).not.toContain("content-length"); - expect(source).not.toContain("c.body("); - }); -}); diff --git a/packages/vitnode/src/api/modules/admin/navigation/lib/read-navigation.test.ts b/packages/vitnode/src/api/modules/admin/navigation/lib/read-navigation.test.ts index cfb0f7f77e..b81f979626 100644 --- a/packages/vitnode/src/api/modules/admin/navigation/lib/read-navigation.test.ts +++ b/packages/vitnode/src/api/modules/admin/navigation/lib/read-navigation.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import { describe, expect, it } from "vitest"; import type { NavigationPreset } from "@/lib/navigation"; diff --git a/packages/vitnode/src/api/modules/admin/navigation/lib/reorder.test.ts b/packages/vitnode/src/api/modules/admin/navigation/lib/reorder.test.ts index 60ab3b6b29..56e59cb921 100644 --- a/packages/vitnode/src/api/modules/admin/navigation/lib/reorder.test.ts +++ b/packages/vitnode/src/api/modules/admin/navigation/lib/reorder.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import { describe, expect, it } from "vitest"; import { reorderNavigationTree } from "./reorder"; diff --git a/packages/vitnode/src/api/modules/admin/roles/routes/delete.route.test.ts b/packages/vitnode/src/api/modules/admin/roles/routes/delete.route.test.ts index 3d6b97d117..6b790accb1 100644 --- a/packages/vitnode/src/api/modules/admin/roles/routes/delete.route.test.ts +++ b/packages/vitnode/src/api/modules/admin/roles/routes/delete.route.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import type { Context } from "hono"; import { OpenAPIHono } from "@hono/zod-openapi"; diff --git a/packages/vitnode/src/api/modules/admin/roles/routes/list.route.test.ts b/packages/vitnode/src/api/modules/admin/roles/routes/list.route.test.ts index 700d40933d..4665d5cc5f 100644 --- a/packages/vitnode/src/api/modules/admin/roles/routes/list.route.test.ts +++ b/packages/vitnode/src/api/modules/admin/roles/routes/list.route.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import type { Context } from "hono"; import { OpenAPIHono } from "@hono/zod-openapi"; diff --git a/packages/vitnode/src/api/modules/admin/routes/session.route.test.ts b/packages/vitnode/src/api/modules/admin/routes/session.route.test.ts index 0b40bca61e..d557339468 100644 --- a/packages/vitnode/src/api/modules/admin/routes/session.route.test.ts +++ b/packages/vitnode/src/api/modules/admin/routes/session.route.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import type { Context } from "hono"; import { OpenAPIHono } from "@hono/zod-openapi"; diff --git a/packages/vitnode/src/api/modules/admin/staff/routes/staff-entry-routes.test.ts b/packages/vitnode/src/api/modules/admin/staff/routes/staff-entry-routes.test.ts index 5a3734d613..fb14187bac 100644 --- a/packages/vitnode/src/api/modules/admin/staff/routes/staff-entry-routes.test.ts +++ b/packages/vitnode/src/api/modules/admin/staff/routes/staff-entry-routes.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import type { Context } from "hono"; import { OpenAPIHono } from "@hono/zod-openapi"; diff --git a/packages/vitnode/src/api/modules/admin/users/lib/assert-edit-user-permission.test.ts b/packages/vitnode/src/api/modules/admin/users/lib/assert-edit-user-permission.test.ts index 6f74677af3..00c1d10bec 100644 --- a/packages/vitnode/src/api/modules/admin/users/lib/assert-edit-user-permission.test.ts +++ b/packages/vitnode/src/api/modules/admin/users/lib/assert-edit-user-permission.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import type { Context } from "hono"; import { HTTPException } from "hono/http-exception"; diff --git a/packages/vitnode/src/api/modules/admin/users/routes/devices.route.test.ts b/packages/vitnode/src/api/modules/admin/users/routes/devices.route.test.ts index cd86e606c4..b0f9e04c39 100644 --- a/packages/vitnode/src/api/modules/admin/users/routes/devices.route.test.ts +++ b/packages/vitnode/src/api/modules/admin/users/routes/devices.route.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import type { Context } from "hono"; import { OpenAPIHono } from "@hono/zod-openapi"; diff --git a/packages/vitnode/src/api/modules/admin/users/routes/notification-preferences.route.test.ts b/packages/vitnode/src/api/modules/admin/users/routes/notification-preferences.route.test.ts index ce8c0fde48..27b04e1e9d 100644 --- a/packages/vitnode/src/api/modules/admin/users/routes/notification-preferences.route.test.ts +++ b/packages/vitnode/src/api/modules/admin/users/routes/notification-preferences.route.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import type { Context } from "hono"; import { OpenAPIHono } from "@hono/zod-openapi"; diff --git a/packages/vitnode/src/api/modules/admin/users/routes/passkeys.route.test.ts b/packages/vitnode/src/api/modules/admin/users/routes/passkeys.route.test.ts index 97c7ba0ca6..6d6b1d25ec 100644 --- a/packages/vitnode/src/api/modules/admin/users/routes/passkeys.route.test.ts +++ b/packages/vitnode/src/api/modules/admin/users/routes/passkeys.route.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import type { Context } from "hono"; import { OpenAPIHono } from "@hono/zod-openapi"; diff --git a/packages/vitnode/src/api/modules/admin/users/routes/password.route.test.ts b/packages/vitnode/src/api/modules/admin/users/routes/password.route.test.ts index 124211a1d7..b6d0b07bcb 100644 --- a/packages/vitnode/src/api/modules/admin/users/routes/password.route.test.ts +++ b/packages/vitnode/src/api/modules/admin/users/routes/password.route.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import type { Context } from "hono"; import { OpenAPIHono } from "@hono/zod-openapi"; diff --git a/packages/vitnode/src/api/modules/admin/users/routes/show.route.test.ts b/packages/vitnode/src/api/modules/admin/users/routes/show.route.test.ts deleted file mode 100644 index 3d687ad8ad..0000000000 --- a/packages/vitnode/src/api/modules/admin/users/routes/show.route.test.ts +++ /dev/null @@ -1,129 +0,0 @@ -// @vitest-environment node -import type { Context } from "hono"; - -import { OpenAPIHono } from "@hono/zod-openapi"; -import { describe, expect, it } from "vitest"; - -import type { PermissionsStaffArgs } from "@/api/lib/permission-staff"; - -import { core_admin_permissions } from "@/database/admins"; -import { core_moderators_permissions } from "@/database/moderators"; -import { core_roles } from "@/database/roles"; -import { core_users, core_users_secondary_roles } from "@/database/users"; -import { createTestCache } from "@/tests/cache"; -import { createMemoryDb } from "@/tests/memory-db"; -import { grantStaffPermissions } from "@/tests/staff-permissions"; - -import { showUserAdminRoute } from "./show.route"; - -const ROOT_ROLE = 1; -const VIEWER_ROLE = 2; -const MEMBER_ROLE = 3; - -const VIEWER = { email: "viewer@example.com", id: 1, roleId: VIEWER_ROLE }; -const TARGET_ID = 7; - -const CAN_VIEW: PermissionsStaffArgs = { - module: "users", - permission: "can_view", - plugin: "@vitnode/core", -}; - -type TargetShape = - | "admin" - | "member" - | "moderator" - | "root via a secondary role"; - -const entryRow = (entry: Record) => ({ - permissions: [], - roleId: null, - unrestricted: false, - userId: null, - ...entry, -}); - -const showTarget = async (target: TargetShape) => { - const cache = createTestCache(); - await grantStaffPermissions(cache, { - permissions: [CAN_VIEW], - userId: VIEWER.id, - }); - - const memory = createMemoryDb([ - [ - core_users, - [ - VIEWER, - { - email: "target@example.com", - id: TARGET_ID, - name: "Target", - nameCode: "target", - roleId: MEMBER_ROLE, - }, - ], - ], - [ - core_users_secondary_roles, - target === "root via a secondary role" - ? [{ roleId: ROOT_ROLE, userId: TARGET_ID }] - : [], - ], - [ - core_roles, - [ - { guest: false, id: ROOT_ROLE, root: true }, - { guest: false, id: VIEWER_ROLE, root: false }, - { guest: false, id: MEMBER_ROLE, root: false }, - ], - ], - [ - core_admin_permissions, - [ - entryRow({ permissions: [CAN_VIEW], roleId: VIEWER_ROLE }), - ...(target === "admin" ? [entryRow({ userId: TARGET_ID })] : []), - ], - ], - [ - core_moderators_permissions, - target === "moderator" ? [entryRow({ userId: TARGET_ID })] : [], - ], - ]); - - const app = new OpenAPIHono(); - app.use("*", async (c, next) => { - c.set("admin", { user: VIEWER } as unknown as Context["var"]["admin"]); - c.set("cache", cache); - c.set("db", memory.db as unknown as Context["var"]["db"]); - await next(); - }); - app.openapi(showUserAdminRoute.route, showUserAdminRoute.handler); - - return await app.request(`/${TARGET_ID}`); -}; - -describe("GET /admin/users/{id} - staff flag", () => { - it.each(["moderator", "root via a secondary role", "admin"])( - "reports a target who is %s as staff", - async target => { - const response = await showTarget(target); - - expect(response.status).toBe(200); - expect(await response.json()).toMatchObject({ - id: TARGET_ID, - isStaff: true, - }); - }, - ); - - it("reports a plain member as not staff", async () => { - const response = await showTarget("member"); - - expect(response.status).toBe(200); - expect(await response.json()).toMatchObject({ - id: TARGET_ID, - isStaff: false, - }); - }); -}); diff --git a/packages/vitnode/src/api/modules/admin/users/routes/sso.route.test.ts b/packages/vitnode/src/api/modules/admin/users/routes/sso.route.test.ts index 184163c775..7c5331522b 100644 --- a/packages/vitnode/src/api/modules/admin/users/routes/sso.route.test.ts +++ b/packages/vitnode/src/api/modules/admin/users/routes/sso.route.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import type { Context } from "hono"; import { OpenAPIHono } from "@hono/zod-openapi"; diff --git a/packages/vitnode/src/api/modules/ai/routes/stream.route.test.ts b/packages/vitnode/src/api/modules/ai/routes/stream.route.test.ts index 9fc2b04d01..8a2f952099 100644 --- a/packages/vitnode/src/api/modules/ai/routes/stream.route.test.ts +++ b/packages/vitnode/src/api/modules/ai/routes/stream.route.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import type { Context } from "hono"; import { OpenAPIHono } from "@hono/zod-openapi"; diff --git a/packages/vitnode/src/api/modules/content/helpers/execute-content-schedule.test.ts b/packages/vitnode/src/api/modules/content/helpers/execute-content-schedule.test.ts index 46ef9a0f61..9a0f103a77 100644 --- a/packages/vitnode/src/api/modules/content/helpers/execute-content-schedule.test.ts +++ b/packages/vitnode/src/api/modules/content/helpers/execute-content-schedule.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import type { SQL } from "drizzle-orm"; import type { Context } from "hono"; diff --git a/packages/vitnode/src/api/modules/cron/cron/clean.cron.test.ts b/packages/vitnode/src/api/modules/cron/cron/clean.cron.test.ts deleted file mode 100644 index 6d66ffbac9..0000000000 --- a/packages/vitnode/src/api/modules/cron/cron/clean.cron.test.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { PgDialect } from "drizzle-orm/pg-core"; -import { drizzle } from "drizzle-orm/postgres-js"; -import { describe, expect, it } from "vitest"; - -import { isOrphanedDevice } from "./clean.cron"; - -describe("isOrphanedDevice", () => { - it("keeps devices referenced by public or admin sessions", () => { - const { sql } = new PgDialect().sqlToQuery( - isOrphanedDevice(drizzle.mock()), - ); - - expect(sql.match(/not exists/g)).toHaveLength(2); - expect(sql).toContain('from "core_sessions"'); - expect(sql).toContain('from "core_admin_sessions"'); - expect(sql).toContain( - '"core_sessions"."deviceId" = "core_sessions_known_devices"."id"', - ); - expect(sql).toContain( - '"core_admin_sessions"."deviceId" = "core_sessions_known_devices"."id"', - ); - }); -}); diff --git a/packages/vitnode/src/api/modules/middleware/route.test.ts b/packages/vitnode/src/api/modules/middleware/route.test.ts index 6a5b1f3601..4098a0a866 100644 --- a/packages/vitnode/src/api/modules/middleware/route.test.ts +++ b/packages/vitnode/src/api/modules/middleware/route.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import type { Context } from "hono"; import { OpenAPIHono } from "@hono/zod-openapi"; diff --git a/packages/vitnode/src/api/modules/pages/page-layout-routes.test.ts b/packages/vitnode/src/api/modules/pages/page-layout-routes.test.ts index 5f7f415294..3b3fc28623 100644 --- a/packages/vitnode/src/api/modules/pages/page-layout-routes.test.ts +++ b/packages/vitnode/src/api/modules/pages/page-layout-routes.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import type { MiddlewareHandler } from "hono"; import { OpenAPIHono } from "@hono/zod-openapi"; diff --git a/packages/vitnode/src/api/modules/search/routes/timeline.route.test.ts b/packages/vitnode/src/api/modules/search/routes/timeline.route.test.ts index 508cddd041..18d7568933 100644 --- a/packages/vitnode/src/api/modules/search/routes/timeline.route.test.ts +++ b/packages/vitnode/src/api/modules/search/routes/timeline.route.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import type { Context } from "hono"; import { OpenAPIHono } from "@hono/zod-openapi"; diff --git a/packages/vitnode/src/api/modules/users/passkeys/passkeys.test.ts b/packages/vitnode/src/api/modules/users/passkeys/passkeys.test.ts index 58826ccc08..77e1724131 100644 --- a/packages/vitnode/src/api/modules/users/passkeys/passkeys.test.ts +++ b/packages/vitnode/src/api/modules/users/passkeys/passkeys.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import type { Context } from "hono"; import { OpenAPIHono } from "@hono/zod-openapi"; diff --git a/packages/vitnode/src/api/modules/users/routes/credentials-hardening.test.ts b/packages/vitnode/src/api/modules/users/routes/credentials-hardening.test.ts index 34595c9a0a..7a50c5a0b3 100644 --- a/packages/vitnode/src/api/modules/users/routes/credentials-hardening.test.ts +++ b/packages/vitnode/src/api/modules/users/routes/credentials-hardening.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import type { Context } from "hono"; import { OpenAPIHono } from "@hono/zod-openapi"; diff --git a/packages/vitnode/src/api/modules/users/routes/devices.route.test.ts b/packages/vitnode/src/api/modules/users/routes/devices.route.test.ts index bdb90474e1..a5bead6462 100644 --- a/packages/vitnode/src/api/modules/users/routes/devices.route.test.ts +++ b/packages/vitnode/src/api/modules/users/routes/devices.route.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import { describe, expect, it } from "vitest"; import { diff --git a/packages/vitnode/src/api/modules/users/routes/email-verification.test.ts b/packages/vitnode/src/api/modules/users/routes/email-verification.test.ts index 8ad110a09b..3e1f080aba 100644 --- a/packages/vitnode/src/api/modules/users/routes/email-verification.test.ts +++ b/packages/vitnode/src/api/modules/users/routes/email-verification.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import type { Context } from "hono"; import { OpenAPIHono } from "@hono/zod-openapi"; diff --git a/packages/vitnode/src/api/modules/users/routes/password-sign-in.test.ts b/packages/vitnode/src/api/modules/users/routes/password-sign-in.test.ts index 5ee20cf25f..622622e6bb 100644 --- a/packages/vitnode/src/api/modules/users/routes/password-sign-in.test.ts +++ b/packages/vitnode/src/api/modules/users/routes/password-sign-in.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import type { Context } from "hono"; import { OpenAPIHono } from "@hono/zod-openapi"; diff --git a/packages/vitnode/src/api/modules/users/routes/revoke-device.test.ts b/packages/vitnode/src/api/modules/users/routes/revoke-device.test.ts index bb781886b1..f7341d0c5b 100644 --- a/packages/vitnode/src/api/modules/users/routes/revoke-device.test.ts +++ b/packages/vitnode/src/api/modules/users/routes/revoke-device.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import { describe, expect, it } from "vitest"; import { diff --git a/packages/vitnode/src/api/modules/users/routes/session.route.test.ts b/packages/vitnode/src/api/modules/users/routes/session.route.test.ts index eb23b7db97..2d04861827 100644 --- a/packages/vitnode/src/api/modules/users/routes/session.route.test.ts +++ b/packages/vitnode/src/api/modules/users/routes/session.route.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import type { Context } from "hono"; import { OpenAPIHono } from "@hono/zod-openapi"; diff --git a/packages/vitnode/src/api/modules/users/routes/time-zone.route.test.ts b/packages/vitnode/src/api/modules/users/routes/time-zone.route.test.ts index a3277ad537..c182534b21 100644 --- a/packages/vitnode/src/api/modules/users/routes/time-zone.route.test.ts +++ b/packages/vitnode/src/api/modules/users/routes/time-zone.route.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import type { Context } from "hono"; import { describe, expect, it, vi } from "vitest"; diff --git a/packages/vitnode/src/api/modules/users/sso/connections/connections.test.ts b/packages/vitnode/src/api/modules/users/sso/connections/connections.test.ts index e6f8415322..3c29c49f9b 100644 --- a/packages/vitnode/src/api/modules/users/sso/connections/connections.test.ts +++ b/packages/vitnode/src/api/modules/users/sso/connections/connections.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import type { Context } from "hono"; import { OpenAPIHono } from "@hono/zod-openapi"; diff --git a/packages/vitnode/src/api/modules/users/sso/routes/callback.test.ts b/packages/vitnode/src/api/modules/users/sso/routes/callback.test.ts index be40138b14..c120c516ae 100644 --- a/packages/vitnode/src/api/modules/users/sso/routes/callback.test.ts +++ b/packages/vitnode/src/api/modules/users/sso/routes/callback.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import type { Context } from "hono"; import { OpenAPIHono } from "@hono/zod-openapi"; diff --git a/packages/vitnode/src/blocks/boundaries.test.ts b/packages/vitnode/src/blocks/boundaries.test.ts index b10498e2b5..232fd8b5e8 100644 --- a/packages/vitnode/src/blocks/boundaries.test.ts +++ b/packages/vitnode/src/blocks/boundaries.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import { readFileSync } from "node:fs"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; diff --git a/packages/vitnode/src/blocks/built-in/links.test.tsx b/packages/vitnode/src/blocks/built-in/links.test.tsx index 99b3d5001f..125e8be83d 100644 --- a/packages/vitnode/src/blocks/built-in/links.test.tsx +++ b/packages/vitnode/src/blocks/built-in/links.test.tsx @@ -1,4 +1,3 @@ -// @vitest-environment jsdom import { render, screen } from "@testing-library/react"; import { describe, expect, it } from "vitest"; diff --git a/packages/vitnode/src/blocks/define.test.ts b/packages/vitnode/src/blocks/define.test.ts index 3039435145..b597692557 100644 --- a/packages/vitnode/src/blocks/define.test.ts +++ b/packages/vitnode/src/blocks/define.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import { describe, expect, it } from "vitest"; import type { AnyBlockVariantDefinition } from "./types"; diff --git a/packages/vitnode/src/blocks/edit-widgets-host.test.tsx b/packages/vitnode/src/blocks/edit-widgets-host.test.tsx index 2bd286fce8..ef1b0bf52f 100644 --- a/packages/vitnode/src/blocks/edit-widgets-host.test.tsx +++ b/packages/vitnode/src/blocks/edit-widgets-host.test.tsx @@ -1,4 +1,3 @@ -// @vitest-environment jsdom import { render, type RenderResult, screen } from "@testing-library/react"; import { act, type ReactNode, useEffect } from "react"; import { describe, expect, it, vi } from "vitest"; @@ -165,16 +164,6 @@ const slidOut = async (): Promise => { }); }; -const shell = (container: HTMLElement): HTMLElement => { - const host = container.firstElementChild; - - if (!(host instanceof HTMLElement)) { - throw new Error("the site shell never rendered"); - } - - return host; -}; - describe("the Edit widgets action the header offers", () => { it("is offered by an editable page the visitor may edit", async () => { render(site(editablePage(true))); @@ -264,42 +253,9 @@ describe("walking away from the page being edited", () => { }); }); -describe("the room the site leaves the editing sidebar", () => { - const reserved = (container: HTMLElement): boolean => - shell(container).className.includes("md:pe-(--editor-sidebar-width)"); - - it("takes none of it while nobody is editing", async () => { - const { container } = render(site(editablePage(true))); - await settled(); - - expect(reserved(container)).toBe(false); - expect(shell(container).className).toContain("transition-[padding]"); - }); - - it("reserves the sidebar's width from the whole site once editing opens", async () => { - const { container } = render(site(editablePage(true))); - await settled(); - await open(); - - expect(reserved(container)).toBe(true); - expect(shell(container).className).toContain("pb-(--editor-sheet-height)"); - }); - - it("keeps the box it pads, so both directions are a transition and not a jump", async () => { - const { container } = render(site(editablePage(true))); - await settled(); - - const before = shell(container); - - await open(); - - expect(shell(container)).toBe(before); - expect(before.style.display).toBe(""); - expect(before.className).toContain("transition-[padding]"); - }); - - it("gives it back the moment editing ends, and keeps the sidebar until it has slid out", async () => { - const { container } = render(site(editablePage(true))); +describe("leaving edit mode", () => { + it("keeps the sidebar until it has slid out", async () => { + render(site(editablePage(true))); await settled(); await open(); @@ -308,8 +264,6 @@ describe("the room the site leaves the editing sidebar", () => { }); await settled(); - expect(reserved(container)).toBe(false); - expect(shell(container).className).toContain("transition-[padding]"); expect(editorIsOpen()).toBe(true); await slidOut(); diff --git a/packages/vitnode/src/blocks/edit.test.tsx b/packages/vitnode/src/blocks/edit.test.tsx index d86311e360..46636db4be 100644 --- a/packages/vitnode/src/blocks/edit.test.tsx +++ b/packages/vitnode/src/blocks/edit.test.tsx @@ -1,4 +1,3 @@ -// @vitest-environment jsdom import { render } from "@testing-library/react"; import { act, diff --git a/packages/vitnode/src/blocks/instance.test.ts b/packages/vitnode/src/blocks/instance.test.ts index ad7f2ee2e1..3832fe0831 100644 --- a/packages/vitnode/src/blocks/instance.test.ts +++ b/packages/vitnode/src/blocks/instance.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import { afterEach, describe, expect, it, vi } from "vitest"; import { diff --git a/packages/vitnode/src/blocks/page-save.test.tsx b/packages/vitnode/src/blocks/page-save.test.tsx index 67ea9a1257..04f2463517 100644 --- a/packages/vitnode/src/blocks/page-save.test.tsx +++ b/packages/vitnode/src/blocks/page-save.test.tsx @@ -1,4 +1,3 @@ -// @vitest-environment jsdom import { render, screen } from "@testing-library/react"; import { act, type ReactNode } from "react"; import { describe, expect, it, vi } from "vitest"; diff --git a/packages/vitnode/src/blocks/page.test.tsx b/packages/vitnode/src/blocks/page.test.tsx index 8277705865..300fdc9197 100644 --- a/packages/vitnode/src/blocks/page.test.tsx +++ b/packages/vitnode/src/blocks/page.test.tsx @@ -1,4 +1,3 @@ -// @vitest-environment jsdom import { render, screen } from "@testing-library/react"; import { act, type ReactNode } from "react"; import { afterEach, describe, expect, it, vi } from "vitest"; diff --git a/packages/vitnode/src/blocks/registry.test.ts b/packages/vitnode/src/blocks/registry.test.ts index eabb7a95be..e580f74076 100644 --- a/packages/vitnode/src/blocks/registry.test.ts +++ b/packages/vitnode/src/blocks/registry.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import { describe, expect, it } from "vitest"; import type { BlockPluginSource } from "./types"; diff --git a/packages/vitnode/src/blocks/renderer.test.tsx b/packages/vitnode/src/blocks/renderer.test.tsx index b996cc7556..685b64c3d1 100644 --- a/packages/vitnode/src/blocks/renderer.test.tsx +++ b/packages/vitnode/src/blocks/renderer.test.tsx @@ -1,4 +1,3 @@ -// @vitest-environment jsdom import { render, screen } from "@testing-library/react"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; diff --git a/packages/vitnode/src/blocks/shape.test.ts b/packages/vitnode/src/blocks/shape.test.ts index 59160d4870..a51da98f4e 100644 --- a/packages/vitnode/src/blocks/shape.test.ts +++ b/packages/vitnode/src/blocks/shape.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import { describe, expect, it } from "vitest"; import type { AnyBlockDefinition } from "./types"; diff --git a/packages/vitnode/src/blocks/validate-content-field.test.ts b/packages/vitnode/src/blocks/validate-content-field.test.ts index e266223208..c7ba12166e 100644 --- a/packages/vitnode/src/blocks/validate-content-field.test.ts +++ b/packages/vitnode/src/blocks/validate-content-field.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import { beforeAll, describe, expect, it } from "vitest"; import { defineContentType } from "../content/define"; diff --git a/packages/vitnode/src/blocks/validate.test.ts b/packages/vitnode/src/blocks/validate.test.ts index 00bcd6ddc8..4313fd3503 100644 --- a/packages/vitnode/src/blocks/validate.test.ts +++ b/packages/vitnode/src/blocks/validate.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import { describe, expect, it } from "vitest"; import type { AnyBlockInstance, BlockAreaInstance } from "./types"; diff --git a/packages/vitnode/src/blocks/variant.test.ts b/packages/vitnode/src/blocks/variant.test.ts index 09cbdfadc1..dd0bd7cd34 100644 --- a/packages/vitnode/src/blocks/variant.test.ts +++ b/packages/vitnode/src/blocks/variant.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import { describe, expect, it } from "vitest"; import { field } from "../content/fields"; diff --git a/packages/vitnode/src/blocks/zone-meta.test.ts b/packages/vitnode/src/blocks/zone-meta.test.ts index 8c2c809e4a..610ce9c8de 100644 --- a/packages/vitnode/src/blocks/zone-meta.test.ts +++ b/packages/vitnode/src/blocks/zone-meta.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import { describe, expect, it } from "vitest"; import type { BlockAllowedSpec } from "./types"; diff --git a/packages/vitnode/src/components/ai/ai-field-assist.test.tsx b/packages/vitnode/src/components/ai/ai-field-assist.test.tsx index 9da1c8abe7..5d189b3aad 100644 --- a/packages/vitnode/src/components/ai/ai-field-assist.test.tsx +++ b/packages/vitnode/src/components/ai/ai-field-assist.test.tsx @@ -1,4 +1,3 @@ -// @vitest-environment jsdom import { fireEvent, render, screen } from "@testing-library/react"; import { IntlProvider } from "use-intl"; import { describe, expect, it, vi } from "vitest"; diff --git a/packages/vitnode/src/components/form/auto-form.test.tsx b/packages/vitnode/src/components/form/auto-form.test.tsx deleted file mode 100644 index c0beae776a..0000000000 --- a/packages/vitnode/src/components/form/auto-form.test.tsx +++ /dev/null @@ -1,296 +0,0 @@ -// @vitest-environment jsdom -import { fireEvent, render, screen, waitFor } from "@testing-library/react"; -import React from "react"; -import { afterEach, describe, expect, it, vi } from "vitest"; -import z from "zod"; - -import type { ItemAutoFormComponentProps } from "./auto-form"; - -import { FormControl, FormMessage } from "../ui/form"; -import { AutoForm } from "./auto-form"; -import { AutoFormDesc } from "./common/desc"; - -afterEach(() => { - vi.unstubAllGlobals(); -}); - -const LabelContext = React.createContext("first label"); - -const formSchema = z.object({ title: z.string() }); - -let mountCount = 0; - -const TitleField = ({ field }: ItemAutoFormComponentProps) => { - const [id] = React.useState(() => `title-control-${(mountCount += 1)}`); - const label = React.use(LabelContext); - - return ( - - ); -}; - -const Fields = React.memo(() => ( - -)); -Fields.displayName = "Fields"; - -const Harness = () => { - const [label, setLabel] = React.useState("first label"); - - return ( - - - - - ); -}; - -describe("a field component that reads context and keeps its own state", () => { - it("survives a context change that re-renders it alone", () => { - render(); - const input = screen.getByLabelText("first label"); - fireEvent.change(input, { target: { value: "Hello" } }); - - fireEvent.click(screen.getByRole("button", { name: "rename" })); - - const renamed = screen.getByLabelText("second label"); - expect(renamed.value).toBe("Hello"); - expect(renamed.id).toBe(input.id); - }); -}); - -const requiredSchema = z.object({ - title: z.string().min(1, { message: "title is required" }).default(""), -}); - -const RequiredTitleField = ({ field }: ItemAutoFormComponentProps) => ( - <> - - - -); - -describe("a form that can be submitted while invalid", () => { - it("keeps the submit button enabled, reveals the errors and focuses the first invalid field", async () => { - const onSubmit = vi.fn(); - render( - , - ); - const submit = screen.getByRole("button", { name: "core.global.submit" }); - - expect(submit).toHaveProperty("disabled", false); - expect(screen.queryByText("title is required")).toBeNull(); - - fireEvent.click(submit); - - expect(await screen.findByText("title is required")).toBeDefined(); - await waitFor(() => { - expect(document.activeElement).toBe(screen.getByLabelText("title")); - }); - expect(onSubmit).not.toHaveBeenCalled(); - }); - - it("shows a field's error once it has been left, then keeps it live", async () => { - render( - , - ); - const input = screen.getByLabelText("title"); - - fireEvent.change(input, { target: { value: "Hello" } }); - fireEvent.change(input, { target: { value: "" } }); - expect(screen.queryByText("title is required")).toBeNull(); - - fireEvent.blur(input); - expect(await screen.findByText("title is required")).toBeDefined(); - - fireEvent.change(input, { target: { value: "Hello again" } }); - await waitFor(() => { - expect(screen.queryByText("title is required")).toBeNull(); - }); - }); - - it("reveals the errors on click without calling onSubmit", async () => { - const onSubmit = vi.fn(); - render( - , - ); - const submit = screen.getByRole("button", { name: "core.global.submit" }); - expect(screen.queryByText("title is required")).toBeNull(); - - expect(submit).toHaveProperty("disabled", false); - fireEvent.click(submit); - - expect(await screen.findByText("title is required")).toBeDefined(); - expect(onSubmit).not.toHaveBeenCalled(); - }); - - it("submits once the values are valid", async () => { - const onSubmit = vi.fn(); - render( - , - ); - - fireEvent.change(screen.getByLabelText("title"), { - target: { value: "Hello" }, - }); - fireEvent.click(screen.getByRole("button", { name: "core.global.submit" })); - - await waitFor(() => { - expect(onSubmit).toHaveBeenCalledWith( - { title: "Hello" }, - expect.anything(), - expect.anything(), - ); - }); - }); -}); - -const DescribedTitleField = ({ field }: ItemAutoFormComponentProps) => ( - <> - - - - - Shown on the card - - -); - -const describedTextOf = (element: HTMLElement) => - (element.getAttribute("aria-describedby") ?? "") - .split(" ") - .map(id => document.getElementById(id)?.textContent) - .filter(Boolean); - -describe("a field control described by its description and message", () => { - it("points aria-describedby at the description, then the error too", async () => { - render( - , - ); - const input = screen.getByLabelText("title"); - - expect(describedTextOf(input)).toEqual(["Shown on the card"]); - - fireEvent.click(screen.getByRole("button", { name: "core.global.submit" })); - - await waitFor(() => { - expect(describedTextOf(input)).toEqual([ - "Shown on the card", - "title is required", - ]); - }); - }); -}); - -const tabbedSchema = z.object({ - title: z.string().default("Hello"), - slug: z.string().min(1, { message: "slug is required" }).default(""), -}); - -const SlugField = ({ field }: ItemAutoFormComponentProps) => ( - <> - - - -); - -describe("a tabbed form with an invalid field on another tab", () => { - it("marks the tab, switches to it and focuses the field", async () => { - vi.stubGlobal( - "ResizeObserver", - class { - disconnect() {} - observe() {} - unobserve() {} - }, - ); - render( - , - ); - - expect(screen.queryByText("core.global.tab_has_errors")).toBeNull(); - - fireEvent.click(screen.getByRole("button", { name: "core.global.submit" })); - - expect(await screen.findByText("core.global.tab_has_errors")).toBeDefined(); - await waitFor(() => { - expect(document.activeElement).toBe(screen.getByLabelText("slug")); - }); - expect( - screen - .getByRole("tab", { name: /Advanced/ }) - .getAttribute("aria-selected"), - ).toBe("true"); - }); -}); - -describe("the submit button's accessible name", () => { - it("matches its visible label", () => { - render( - , - ); - - expect(screen.getByRole("button", { name: "Save category" })).toBeDefined(); - }); -}); diff --git a/packages/vitnode/src/components/form/fields/combobox.test.tsx b/packages/vitnode/src/components/form/fields/combobox.test.tsx deleted file mode 100644 index 05e062c768..0000000000 --- a/packages/vitnode/src/components/form/fields/combobox.test.tsx +++ /dev/null @@ -1,64 +0,0 @@ -// @vitest-environment jsdom -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { render, screen } from "@testing-library/react"; -import { describe, expect, it } from "vitest"; -import z from "zod"; - -import { AutoForm } from "../auto-form"; -import { AutoFormCombobox } from "./combobox"; - -const fetchMembers = () => [ - { label: "Ada Lovelace", value: "1" }, - { label: "Grace Hopper", value: "2" }, -]; - -const renderMemberForm = (formSchema: z.ZodObject) => - render( - - ( - - ), - }, - ]} - formSchema={formSchema} - /> - , - ); - -describe("AutoFormCombobox with a single async value", () => { - it("starts empty instead of showing undefined", () => { - renderMemberForm( - z.object({ member: z.object({ label: z.string(), value: z.string() }) }), - ); - - expect(screen.getByRole("combobox")).toHaveProperty("value", ""); - }); - - it("does not mark a required object field as optional", () => { - renderMemberForm( - z.object({ member: z.object({ label: z.string(), value: z.string() }) }), - ); - - expect(screen.queryByText("core.global.optional")).toBeNull(); - }); - - it("still marks an optional object field as optional", () => { - renderMemberForm( - z.object({ - member: z.object({ label: z.string(), value: z.string() }).optional(), - }), - ); - - expect(screen.getByText("core.global.optional")).toBeDefined(); - }); -}); diff --git a/packages/vitnode/src/components/form/fields/file-dropzone.test.tsx b/packages/vitnode/src/components/form/fields/file-dropzone.test.tsx deleted file mode 100644 index 86da5e8598..0000000000 --- a/packages/vitnode/src/components/form/fields/file-dropzone.test.tsx +++ /dev/null @@ -1,120 +0,0 @@ -// @vitest-environment jsdom -import { fireEvent, render, screen } from "@testing-library/react"; -import { afterEach, describe, expect, it, vi } from "vitest"; - -import { FileDropzone } from "./file-shared"; - -const filesTransfer = (files: File[] = []) => ({ - dataTransfer: { dropEffect: "none", files, types: ["Files"] }, -}); - -const textTransfer = { - dataTransfer: { dropEffect: "none", files: [], types: ["text/plain"] }, -}; - -const renderDropzone = ( - props: Partial> = {}, -) => { - const onPick = vi.fn(); - const view = render( - , - ); - const zone = view.container.querySelector( - "[data-slot=file-dropzone]", - ); - if (!zone) throw new Error("dropzone not rendered"); - - return { onPick, zone }; -}; - -afterEach(() => { - vi.restoreAllMocks(); -}); - -describe("FileDropzone", () => { - it("highlights the moment files are dragged in and stays lit while crossing its children", () => { - const { zone } = renderDropzone(); - const prompt = screen.getByText("Drop a file here"); - - fireEvent.dragEnter(zone, filesTransfer()); - expect(zone.hasAttribute("data-dragging")).toBe(true); - - fireEvent.dragEnter(prompt, filesTransfer()); - fireEvent.dragLeave(zone, filesTransfer()); - expect(zone.hasAttribute("data-dragging")).toBe(true); - - fireEvent.dragLeave(prompt, filesTransfer()); - expect(zone.hasAttribute("data-dragging")).toBe(false); - }); - - it("ignores a drag that carries no files", () => { - const { zone } = renderDropzone(); - - fireEvent.dragEnter(zone, textTransfer); - - expect(zone.hasAttribute("data-dragging")).toBe(false); - }); - - it("hands the dropped files over and clears the highlight", () => { - const { onPick, zone } = renderDropzone(); - const file = new File(["bytes"], "photo.webp", { type: "image/webp" }); - - fireEvent.dragEnter(zone, filesTransfer()); - fireEvent.drop(zone, filesTransfer([file])); - - expect(onPick).toHaveBeenCalledWith([file]); - expect(zone.hasAttribute("data-dragging")).toBe(false); - }); - - it("opens the file dialog once from anywhere in the zone, including its button", () => { - const { zone } = renderDropzone(); - const input = document.querySelector("[data-slot=file-input]"); - const click = vi.fn(); - input?.addEventListener("click", click); - - fireEvent.click(zone); - expect(click).toHaveBeenCalledTimes(1); - - fireEvent.click( - screen.getByRole("button", { name: "core.global.file.choose" }), - ); - expect(click).toHaveBeenCalledTimes(2); - }); - - it("does nothing while disabled", () => { - const file = new File(["bytes"], "photo.webp", { type: "image/webp" }); - const { onPick, zone } = renderDropzone({ disabled: true }); - const input = document.querySelector("[data-slot=file-input]"); - const click = vi.fn(); - input?.addEventListener("click", click); - - fireEvent.click(zone); - fireEvent.dragEnter(zone, filesTransfer()); - fireEvent.drop(zone, filesTransfer([file])); - - expect(click).not.toHaveBeenCalled(); - expect(onPick).not.toHaveBeenCalled(); - expect(zone.hasAttribute("data-dragging")).toBe(false); - }); - - it("shows how far the upload has got and lets it be cancelled", () => { - const onCancel = vi.fn(); - renderDropzone({ onCancel, pending: true, progress: 0.4 }); - - expect(screen.getByText("40%")).toBeDefined(); - expect(screen.getByRole("progressbar").getAttribute("aria-valuenow")).toBe( - "40", - ); - - fireEvent.click( - screen.getByRole("button", { name: "core.global.file.cancel_upload" }), - ); - expect(onCancel).toHaveBeenCalledTimes(1); - }); -}); diff --git a/packages/vitnode/src/components/form/fields/file.test.tsx b/packages/vitnode/src/components/form/fields/file.test.tsx deleted file mode 100644 index 1c5cdf4241..0000000000 --- a/packages/vitnode/src/components/form/fields/file.test.tsx +++ /dev/null @@ -1,104 +0,0 @@ -// @vitest-environment jsdom -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { fireEvent, render, screen } from "@testing-library/react"; -import { describe, expect, it } from "vitest"; -import z from "zod"; - -import type { ItemAutoFormComponentProps } from "../auto-form"; -import type { AutoFormFileDisplay } from "./file-display"; -import type { AutoFormFileValue } from "./file-shared"; - -import { AutoForm } from "../auto-form"; -import { AutoFormFile, AutoFormFileDisplayContext } from "./file"; - -const image: AutoFormFileValue = { - id: 7, - mimeType: "image/webp", - name: "cover.webp", - size: 4096, - url: "https://cdn.test/cover.webp", -}; - -const pdf: AutoFormFileValue = { - id: 7, - mimeType: "application/pdf", - name: "brief.pdf", - size: 4096, - url: "https://cdn.test/brief.pdf", -}; - -const renderFile = ({ - display, - file, -}: { - display?: AutoFormFileDisplay; - file: AutoFormFileValue; -}) => { - const CoverField = (props: ItemAutoFormComponentProps) => ( - await Promise.resolve(file)} - /> - ); - const form = ( - - ); - - return render( - - {display ? ( - - {form} - - ) : ( - form - )} - , - ); -}; - -describe("AutoFormFile cover display", () => { - it("shows a stored image as a cover with replace and remove actions", () => { - const view = renderFile({ display: "cover", file: image }); - - const cover = view.container.querySelector("[data-slot=file-cover]"); - expect(cover?.querySelector("img")?.getAttribute("src")).toBe(image.url); - expect( - screen.getByRole("button", { name: "core.global.file.replace" }), - ).toBeTruthy(); - expect(view.container.querySelector("[data-slot=attachment]")).toBeNull(); - }); - - it("goes back to the dropzone after the cover is removed", () => { - const view = renderFile({ display: "cover", file: image }); - - fireEvent.click( - screen.getByRole("button", { name: "core.global.file.remove" }), - ); - - expect(view.container.querySelector("[data-slot=file-cover]")).toBeNull(); - expect( - view.container.querySelector("[data-slot=file-dropzone]"), - ).toBeTruthy(); - }); - - it("keeps the file card for a file that is not an image", () => { - const view = renderFile({ display: "cover", file: pdf }); - - expect(view.container.querySelector("[data-slot=file-cover]")).toBeNull(); - expect(view.container.querySelector("[data-slot=attachment]")).toBeTruthy(); - }); - - it("keeps the file card without a cover display", () => { - const view = renderFile({ file: image }); - - expect(view.container.querySelector("[data-slot=file-cover]")).toBeNull(); - expect(view.container.querySelector("[data-slot=attachment]")).toBeTruthy(); - }); -}); diff --git a/packages/vitnode/src/components/form/fields/files.test.tsx b/packages/vitnode/src/components/form/fields/files.test.tsx deleted file mode 100644 index 4c5dc95d8e..0000000000 --- a/packages/vitnode/src/components/form/fields/files.test.tsx +++ /dev/null @@ -1,178 +0,0 @@ -// @vitest-environment jsdom -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { act, fireEvent, render, screen, within } from "@testing-library/react"; -import { Toaster } from "sonner"; -import { describe, expect, it, vi } from "vitest"; -import z from "zod"; - -import type { ItemAutoFormComponentProps } from "../auto-form"; -import type { AutoFormFileValue } from "./file-shared"; -import type { FileUploadOptions } from "./file-upload-queue"; - -import { AutoForm } from "../auto-form"; -import { AutoFormFiles } from "./files"; - -const stored = (id: number, name: string): AutoFormFileValue => ({ - id, - mimeType: "application/pdf", - name, - size: 2048, - url: `https://cdn.test/${id}.pdf`, -}); - -const schema = z.object({ gallery: z.array(z.number()).default([1, 2]) }); - -const renderFiles = ( - onUpload: ( - file: File, - options: FileUploadOptions, - ) => Promise, -) => { - const GalleryField = (props: ItemAutoFormComponentProps) => ( - - ); - - const view = render( - - - - , - ); - - const pick = (name: string) => { - const input = view.container.querySelector( - "[data-slot=file-input]", - ); - if (!input) throw new Error("file input not rendered"); - - fireEvent.change(input, { - target: { - files: [new File(["bytes"], name, { type: "application/pdf" })], - }, - }); - }; - - const names = () => - [...view.container.querySelectorAll("[data-slot=attachment-title]")].map( - title => title.getAttribute("title"), - ); - - return { names, pick }; -}; - -const deferred = () => { - let resolve: (value: AutoFormFileValue) => void = () => {}; - let reject: (error: unknown) => void = () => {}; - const promise = new Promise((done, fail) => { - resolve = done; - reject = fail; - }); - - return { promise, reject, resolve }; -}; - -describe("AutoFormFiles", () => { - it("removes a file with an undo that puts it back where it was", async () => { - const { names } = renderFiles(vi.fn()); - - const [removeFirst] = screen.getAllByRole("button", { - name: "core.global.file.remove_named", - }); - fireEvent.click(removeFirst); - expect(names()).toEqual(["second.pdf"]); - - fireEvent.click( - await screen.findByRole("button", { name: "core.global.file.undo" }), - ); - - expect(names()).toEqual(["first.pdf", "second.pdf"]); - }); - - it("shows upload progress and cancels an upload in flight", async () => { - const upload = deferred(); - let options: FileUploadOptions | undefined; - const { names, pick } = renderFiles(async (_file, given) => { - options = given; - - return await upload.promise; - }); - - await act(async () => { - pick("third.pdf"); - await Promise.resolve(); - }); - act(() => { - options?.onProgress(0.25); - }); - - expect(screen.getByText("25%")).toBeDefined(); - - fireEvent.click( - screen.getByRole("button", { - name: "core.global.file.cancel_upload_named", - }), - ); - - expect(options?.signal.aborted).toBe(true); - expect(screen.queryByText("25%")).toBeNull(); - - await act(async () => { - upload.resolve(stored(3, "third.pdf")); - await Promise.resolve(); - }); - - expect(names()).toEqual(["first.pdf", "second.pdf"]); - }); - - it("keeps a failed upload as a card that can be retried", async () => { - const attempts = [deferred(), deferred()]; - let call = 0; - const { names, pick } = renderFiles(async () => { - const attempt = attempts[call]; - call += 1; - - return await attempt.promise; - }); - - await act(async () => { - pick("third.pdf"); - await Promise.resolve(); - }); - await act(async () => { - attempts[0].reject(new Error("The network went away")); - await new Promise(resolve => setTimeout(resolve, 0)); - }); - - const alert = await screen.findByText("The network went away"); - expect(alert.getAttribute("role")).toBe("alert"); - - const failedCard = alert.closest("[data-slot=attachment]"); - if (!failedCard) throw new Error("failed card not rendered"); - - await act(async () => { - fireEvent.click( - within(failedCard).getByRole("button", { - name: "core.global.file.retry_named", - }), - ); - await Promise.resolve(); - }); - await act(async () => { - attempts[1].resolve(stored(3, "third.pdf")); - await new Promise(resolve => setTimeout(resolve, 0)); - }); - - expect(screen.queryByText("The network went away")).toBeNull(); - expect(names()).toEqual(["first.pdf", "second.pdf", "third.pdf"]); - }); -}); diff --git a/packages/vitnode/src/components/form/fields/multi-lang.test.tsx b/packages/vitnode/src/components/form/fields/multi-lang.test.tsx deleted file mode 100644 index adc6940eb5..0000000000 --- a/packages/vitnode/src/components/form/fields/multi-lang.test.tsx +++ /dev/null @@ -1,81 +0,0 @@ -// @vitest-environment jsdom -import { act, renderHook } from "@testing-library/react"; -import React from "react"; -import { describe, expect, it, vi } from "vitest"; - -import type { MultiLangValue } from "@/lib/helpers/multi-lang"; - -import { LanguagesProvider } from "@/components/languages-provider"; - -import { useMultiLangField } from "./multi-lang"; -import { MultiLangLanguageContext } from "./multi-lang-language"; - -const languages = [ - { code: "en", name: "English" }, - { code: "pl", name: "Polski" }, -]; - -const value: MultiLangValue = [ - { languageCode: "en", value: "Hello" }, - { languageCode: "pl", value: "Cześć" }, -]; - -const renderField = (lockedLanguage: null | string) => { - const onChange = vi.fn(); - const wrapper = ({ children }: { children: React.ReactNode }) => ( - - - {children} - - - ); - const { result } = renderHook( - () => - useMultiLangField({ - name: "title", - onBlur: vi.fn(), - onChange, - value, - }), - { wrapper }, - ); - - return { onChange, result }; -}; - -describe("useMultiLangField", () => { - it("lets the user pick a language when nothing locks it", () => { - const { result } = renderField(null); - - expect(result.current.canSelect).toBe(true); - expect(result.current.selected).toBe("en"); - expect(result.current.currentValue).toBe("Hello"); - }); - - it("edits the locked language and hides the picker", () => { - const { onChange, result } = renderField("pl"); - - expect(result.current.canSelect).toBe(false); - expect(result.current.selected).toBe("pl"); - expect(result.current.currentValue).toBe("Cześć"); - - act(() => { - result.current.setValue("Dzień dobry"); - }); - - expect(onChange).toHaveBeenCalledWith([ - { languageCode: "en", value: "Hello" }, - { languageCode: "pl", value: "Dzień dobry" }, - ]); - }); - - it("ignores a language picked before the lock", () => { - const { result } = renderField("pl"); - - act(() => { - result.current.setSelected("en"); - }); - - expect(result.current.selected).toBe("pl"); - }); -}); diff --git a/packages/vitnode/src/components/form/fields/roles-boundaries.test.ts b/packages/vitnode/src/components/form/fields/roles-boundaries.test.ts deleted file mode 100644 index be452022d9..0000000000 --- a/packages/vitnode/src/components/form/fields/roles-boundaries.test.ts +++ /dev/null @@ -1,88 +0,0 @@ -// @vitest-environment node -import { readFileSync } from "node:fs"; -import { dirname, join } from "node:path"; -import { fileURLToPath } from "node:url"; -import { describe, expect, it } from "vitest"; - -import { reachedSpecifiers, runtimeImports } from "@/tests/import-graph"; - -const here = dirname(fileURLToPath(import.meta.url)); - -const SHARED = { - /** The framework-neutral field. */ - field: join(here, "input-roles.tsx"), - /** The type and the search signature, with no imports at all. */ - types: join(here, "roles.ts"), -}; - -const read = (path: string) => readFileSync(path, "utf8"); - -describe("the shared role field is framework-neutral", () => { - it("takes its translations from use-intl", () => { - expect(reachedSpecifiers(SHARED.field)).toContain("use-intl"); - }); - - it("declares its role types in a module that imports nothing", () => { - expect(runtimeImports(SHARED.types)).toEqual([]); - }); - - it("reads RoleOption from that module rather than from a transport", () => { - expect(read(SHARED.field)).toMatch( - /import type \{ RoleOption, RoleSearch \} from "\.\/roles"/, - ); - }); -}); - -describe("the search dependency is injected, and stays injected", () => { - it("is required on the props type", () => { - expect(read(SHARED.field)).toMatch(/\n {2}search: RoleSearch;/); - }); - - it("has no default parameter", () => { - // The destructured parameter, at its own indent - `search={...}` further - // down is the prop handed to `AsyncPicker` and is not what this is about. - const source = read(SHARED.field); - - expect(source).toMatch(/\n {2}search,\n/); - expect(source).not.toMatch(/\n {2}search\s*=/); - }); - - it("has no fallback and no host detection", () => { - const source = read(SHARED.field); - - expect(source).not.toContain("searchRolesLazily"); - expect(source).not.toMatch(/typeof window|process\.env|import\.meta\.env/); - }); - - it("still exports the canonical field and its two types", () => { - // The three names a host binds to. - expect(read(SHARED.field)).toContain("export const AutoFormRoles"); - expect(read(SHARED.types)).toContain("export interface RoleOption"); - expect(read(SHARED.types)).toContain("export type RoleSearch"); - }); - - it("leaves a browser search for the host to pass, in the AdminCP's own module", () => { - // `searchAdminRolesInBrowser` lives with the roles screen's other reads - // rather than beside the field - which is the point: the field does not - // know how roles are found. - const rolesQuery = join( - here, - "../../../views/admin/views/core/users/roles/roles-query.ts", - ); - - expect(read(rolesQuery)).toContain( - "export const searchAdminRolesInBrowser", - ); - }); -}); - -describe("a default search may not return", () => { - it("is not replaced by a default inside the field", () => { - // The specific regression: the tempting fix for a caller that forgot - // `search` is a default here. That would put a transport back inside a - // framework-neutral component and re-close the seam. - const source = read(SHARED.field); - - expect(source).not.toMatch(/search\s*=\s*search[A-Z]/); - }); -}); diff --git a/packages/vitnode/src/components/rich-text/boundaries.test.ts b/packages/vitnode/src/components/rich-text/boundaries.test.ts index 5a9bafe1c6..d57e6608db 100644 --- a/packages/vitnode/src/components/rich-text/boundaries.test.ts +++ b/packages/vitnode/src/components/rich-text/boundaries.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment node import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; import { describe, expect, it } from "vitest"; diff --git a/packages/vitnode/src/components/rich-text/rich-text-content.test.tsx b/packages/vitnode/src/components/rich-text/rich-text-content.test.tsx index 7f78eda1a2..9ecb48cd75 100644 --- a/packages/vitnode/src/components/rich-text/rich-text-content.test.tsx +++ b/packages/vitnode/src/components/rich-text/rich-text-content.test.tsx @@ -1,4 +1,3 @@ -// @vitest-environment jsdom import { render, screen, within } from "@testing-library/react"; import { describe, expect, it } from "vitest"; @@ -103,7 +102,7 @@ describe("RichTextContent", () => { ); const styled = screen.getByText("red"); - expect(styled.style.color).toBe("rgb(255, 0, 0)"); + expect(styled.style.color).toBe("#ff0000"); expect(styled.style.fontSize).toBe("18px"); expect(screen.getByText("evil", { exact: false }).tagName).toBe("P"); expect(container.querySelectorAll("span")).toHaveLength(1); diff --git a/packages/vitnode/src/components/switchers/themes/theme-switchers.test.tsx b/packages/vitnode/src/components/switchers/themes/theme-switchers.test.tsx index 9166004e3f..83210ef823 100644 --- a/packages/vitnode/src/components/switchers/themes/theme-switchers.test.tsx +++ b/packages/vitnode/src/components/switchers/themes/theme-switchers.test.tsx @@ -1,4 +1,3 @@ -// @vitest-environment jsdom import { fireEvent, render, screen, within } from "@testing-library/react"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; diff --git a/packages/vitnode/src/components/table/content.test.tsx b/packages/vitnode/src/components/table/content.test.tsx index 8b11cda0d2..d0e370a689 100644 --- a/packages/vitnode/src/components/table/content.test.tsx +++ b/packages/vitnode/src/components/table/content.test.tsx @@ -1,4 +1,3 @@ -// @vitest-environment jsdom import { fireEvent, render, screen, within } from "@testing-library/react"; import React from "react"; import { describe, expect, it, vi } from "vitest"; diff --git a/packages/vitnode/src/components/table/expansion.test.tsx b/packages/vitnode/src/components/table/expansion.test.tsx index 1797d9cfed..a7c0450b6c 100644 --- a/packages/vitnode/src/components/table/expansion.test.tsx +++ b/packages/vitnode/src/components/table/expansion.test.tsx @@ -1,4 +1,3 @@ -// @vitest-environment jsdom import { fireEvent, render, diff --git a/packages/vitnode/src/components/table/order-table-head.test.tsx b/packages/vitnode/src/components/table/order-table-head.test.tsx index 663ced8b4b..1086efa94b 100644 --- a/packages/vitnode/src/components/table/order-table-head.test.tsx +++ b/packages/vitnode/src/components/table/order-table-head.test.tsx @@ -1,4 +1,3 @@ -// @vitest-environment jsdom import { act, fireEvent, render, screen, within } from "@testing-library/react"; import React from "react"; import { describe, expect, it } from "vitest"; diff --git a/packages/vitnode/src/components/table/reorder.test.tsx b/packages/vitnode/src/components/table/reorder.test.tsx index cfbe1cd6c3..a533077214 100644 --- a/packages/vitnode/src/components/table/reorder.test.tsx +++ b/packages/vitnode/src/components/table/reorder.test.tsx @@ -1,4 +1,3 @@ -// @vitest-environment jsdom import { act, fireEvent, diff --git a/packages/vitnode/src/components/table/table-boundaries.test.ts b/packages/vitnode/src/components/table/table-boundaries.test.ts deleted file mode 100644 index 92539b3e5f..0000000000 --- a/packages/vitnode/src/components/table/table-boundaries.test.ts +++ /dev/null @@ -1,65 +0,0 @@ -// @vitest-environment node -import { readFileSync } from "node:fs"; -import { dirname, join } from "node:path"; -import { fileURLToPath } from "node:url"; -import { describe, expect, it } from "vitest"; - -import { runtimeImports } from "@/tests/import-graph"; - -const here = dirname(fileURLToPath(import.meta.url)); - -const SHARED = { - filters: join(here, "filters.tsx"), - orderHead: join(here, "order-table-head.tsx"), - pagination: join(here, "pagination.tsx"), - search: join(here, "search.tsx"), - urlState: join(here, "url-state.ts"), -}; - -describe("the shared data table is framework-neutral", () => { - it("keeps the URL arithmetic free of every import", () => { - // The point of `url-state.ts`: no router, no React, nothing to mock. If an - // import ever appears here, the seam has started growing a second job. - expect(runtimeImports(SHARED.urlState)).toEqual([]); - }); -}); - -describe("the shared controls take their navigation from the seam", () => { - const withoutComments = (path: string): string => - readFileSync(path, "utf8") - .replace(/\/\*[\s\S]*?\*\//g, "") - .replace(/\/\/.*$/gm, ""); - - const controls = [ - SHARED.filters, - SHARED.orderHead, - SHARED.pagination, - SHARED.search, - ]; - - it("asks the seam where it is rather than a router", () => { - for (const path of controls) { - const code = withoutComments(path); - - expect(code).toContain("useDataTableUrl"); - expect(code).not.toContain("useSearchParams"); - expect(code).not.toContain("useRouter"); - expect(code).not.toContain("usePathname"); - } - }); - - it("builds no URLs of its own", () => { - // Every `new URLSearchParams(...)` in a control was a copy of the current - // search about to be edited by hand. That is `url-state.ts`'s job now, and - // a control that starts doing it again is a rule nobody can test. - for (const path of controls) { - expect(withoutComments(path)).not.toContain("new URLSearchParams"); - } - }); - - it("never names a pathname, because it is not given one", () => { - for (const path of controls) { - expect(withoutComments(path)).not.toContain("pathname"); - } - }); -}); diff --git a/packages/vitnode/src/components/tiptap/collaboration.test.tsx b/packages/vitnode/src/components/tiptap/collaboration.test.tsx deleted file mode 100644 index 024c35e25c..0000000000 --- a/packages/vitnode/src/components/tiptap/collaboration.test.tsx +++ /dev/null @@ -1,25 +0,0 @@ -// @vitest-environment jsdom -import { render, screen } from "@testing-library/react"; -import { describe, expect, it, vi } from "vitest"; - -import { CollaborativeEditorSlot } from "./collaboration"; - -describe("CollaborativeEditorSlot", () => { - it("renders the collaborative editor in the same render, without suspending", () => { - const editor = vi.fn(({ locale }: { locale: null | string }) => ( -

Shared editor ({locale})

- )); - - render( - {}} - render={editor} - value={null} - />, - ); - - expect(screen.getByText("Shared editor (pl)")).toBeTruthy(); - expect(editor).toHaveBeenCalledTimes(1); - }); -}); diff --git a/packages/vitnode/src/components/tiptap/panel.test.ts b/packages/vitnode/src/components/tiptap/panel.test.tsx similarity index 98% rename from packages/vitnode/src/components/tiptap/panel.test.ts rename to packages/vitnode/src/components/tiptap/panel.test.tsx index 948f83368b..d2477d30f7 100644 --- a/packages/vitnode/src/components/tiptap/panel.test.ts +++ b/packages/vitnode/src/components/tiptap/panel.test.tsx @@ -1,4 +1,3 @@ -// @vitest-environment jsdom import { Editor } from "@tiptap/react"; import StarterKit from "@tiptap/starter-kit"; import { afterEach, describe, expect, it } from "vitest"; diff --git a/packages/vitnode/src/components/tiptap/quick-ask/quick-ask-editor.test.ts b/packages/vitnode/src/components/tiptap/quick-ask/quick-ask-editor.test.tsx similarity index 99% rename from packages/vitnode/src/components/tiptap/quick-ask/quick-ask-editor.test.ts rename to packages/vitnode/src/components/tiptap/quick-ask/quick-ask-editor.test.tsx index cea3527ab0..f64fb89e1a 100644 --- a/packages/vitnode/src/components/tiptap/quick-ask/quick-ask-editor.test.ts +++ b/packages/vitnode/src/components/tiptap/quick-ask/quick-ask-editor.test.tsx @@ -1,4 +1,3 @@ -// @vitest-environment jsdom import { Editor } from "@tiptap/react"; import StarterKit from "@tiptap/starter-kit"; import { afterEach, describe, expect, it } from "vitest"; diff --git a/packages/vitnode/src/components/tiptap/slash-command/slash-command-list.test.tsx b/packages/vitnode/src/components/tiptap/slash-command/slash-command-list.test.tsx index 0e293f36de..9b3c7adda9 100644 --- a/packages/vitnode/src/components/tiptap/slash-command/slash-command-list.test.tsx +++ b/packages/vitnode/src/components/tiptap/slash-command/slash-command-list.test.tsx @@ -1,4 +1,3 @@ -// @vitest-environment jsdom import type { SuggestionKeyDownProps } from "@tiptap/suggestion"; import { act, fireEvent, render, screen, within } from "@testing-library/react"; diff --git a/packages/vitnode/src/components/tiptap/submit-form-shortcut.test.ts b/packages/vitnode/src/components/tiptap/submit-form-shortcut.test.tsx similarity index 97% rename from packages/vitnode/src/components/tiptap/submit-form-shortcut.test.ts rename to packages/vitnode/src/components/tiptap/submit-form-shortcut.test.tsx index 7a7a708bc8..57b8ab559a 100644 --- a/packages/vitnode/src/components/tiptap/submit-form-shortcut.test.ts +++ b/packages/vitnode/src/components/tiptap/submit-form-shortcut.test.tsx @@ -1,4 +1,3 @@ -// @vitest-environment jsdom import { Editor } from "@tiptap/react"; import StarterKit from "@tiptap/starter-kit"; import { afterEach, describe, expect, it, vi } from "vitest"; diff --git a/packages/vitnode/src/components/tiptap/toolbar/actions/quick-ask-action.test.tsx b/packages/vitnode/src/components/tiptap/toolbar/actions/quick-ask-action.test.tsx index 1a88fa19a0..e881314266 100644 --- a/packages/vitnode/src/components/tiptap/toolbar/actions/quick-ask-action.test.tsx +++ b/packages/vitnode/src/components/tiptap/toolbar/actions/quick-ask-action.test.tsx @@ -1,4 +1,3 @@ -// @vitest-environment jsdom import { fireEvent, render, screen } from "@testing-library/react"; import { IntlProvider } from "use-intl"; import { describe, expect, it, vi } from "vitest"; diff --git a/packages/vitnode/src/components/tiptap/toolbar/tiptap-toolbar.test.tsx b/packages/vitnode/src/components/tiptap/toolbar/tiptap-toolbar.test.tsx index c2f90947ad..a8c014617d 100644 --- a/packages/vitnode/src/components/tiptap/toolbar/tiptap-toolbar.test.tsx +++ b/packages/vitnode/src/components/tiptap/toolbar/tiptap-toolbar.test.tsx @@ -1,4 +1,3 @@ -// @vitest-environment jsdom import { cleanup, fireEvent, diff --git a/packages/vitnode/src/components/ui/alert-dialog.test.tsx b/packages/vitnode/src/components/ui/alert-dialog.test.tsx deleted file mode 100644 index c969860318..0000000000 --- a/packages/vitnode/src/components/ui/alert-dialog.test.tsx +++ /dev/null @@ -1,47 +0,0 @@ -// @vitest-environment jsdom -import { render, screen } from "@testing-library/react"; -import { describe, expect, it } from "vitest"; - -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogTitle, -} from "./alert-dialog"; - -describe("AlertDialog buttons", () => { - it("announce their visible text", () => { - render( - - - Delete this thread? - Keep it - Delete thread - - , - ); - - expect(screen.getByRole("button", { name: "Keep it" })).toBeDefined(); - expect(screen.getByRole("button", { name: "Delete thread" })).toBeDefined(); - }); - - it("fall back to translated labels without text", () => { - render( - - - Delete this thread? - - - - , - ); - - expect( - screen.getByRole("button", { name: "core.global.cancel" }), - ).toBeDefined(); - expect( - screen.getByRole("button", { name: "core.global.confirm" }), - ).toBeDefined(); - }); -}); diff --git a/packages/vitnode/src/components/ui/button.test.tsx b/packages/vitnode/src/components/ui/button.test.tsx index 7daad518fe..78c6ed115f 100644 --- a/packages/vitnode/src/components/ui/button.test.tsx +++ b/packages/vitnode/src/components/ui/button.test.tsx @@ -1,4 +1,3 @@ -// @vitest-environment jsdom import { fireEvent, render, screen } from "@testing-library/react"; import { describe, expect, it, vi } from "vitest"; diff --git a/packages/vitnode/src/components/ui/carousel.test.tsx b/packages/vitnode/src/components/ui/carousel.test.tsx index 5ee9394a29..02d9d9a709 100644 --- a/packages/vitnode/src/components/ui/carousel.test.tsx +++ b/packages/vitnode/src/components/ui/carousel.test.tsx @@ -1,4 +1,3 @@ -// @vitest-environment jsdom import { act, fireEvent, render, screen } from "@testing-library/react"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; diff --git a/packages/vitnode/src/components/ui/cascader.test.tsx b/packages/vitnode/src/components/ui/cascader.test.tsx index 125c3f85c2..199653f432 100644 --- a/packages/vitnode/src/components/ui/cascader.test.tsx +++ b/packages/vitnode/src/components/ui/cascader.test.tsx @@ -1,4 +1,3 @@ -// @vitest-environment jsdom import { fireEvent, render, screen, waitFor } from "@testing-library/react"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; diff --git a/packages/vitnode/src/components/ui/chart.test.tsx b/packages/vitnode/src/components/ui/chart.test.tsx index 27c18c211d..f3fed852a3 100644 --- a/packages/vitnode/src/components/ui/chart.test.tsx +++ b/packages/vitnode/src/components/ui/chart.test.tsx @@ -1,4 +1,3 @@ -// @vitest-environment jsdom import type { ChartPoint } from "@tanstack/charts"; import { render, screen, within } from "@testing-library/react"; diff --git a/packages/vitnode/src/components/ui/copy-button.test.tsx b/packages/vitnode/src/components/ui/copy-button.test.tsx index a54f96f557..f664fcc66a 100644 --- a/packages/vitnode/src/components/ui/copy-button.test.tsx +++ b/packages/vitnode/src/components/ui/copy-button.test.tsx @@ -1,4 +1,3 @@ -// @vitest-environment jsdom import { act, fireEvent, render, screen } from "@testing-library/react"; import { toast } from "sonner"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; diff --git a/packages/vitnode/src/components/ui/counter.test.tsx b/packages/vitnode/src/components/ui/counter.test.tsx index c8d1db7111..11d3385966 100644 --- a/packages/vitnode/src/components/ui/counter.test.tsx +++ b/packages/vitnode/src/components/ui/counter.test.tsx @@ -1,4 +1,3 @@ -// @vitest-environment jsdom import { fireEvent, render, screen } from "@testing-library/react"; import React from "react"; import { describe, expect, it, vi } from "vitest"; diff --git a/packages/vitnode/src/components/ui/date-picker.test.tsx b/packages/vitnode/src/components/ui/date-picker.test.tsx index 299612b474..4834918acf 100644 --- a/packages/vitnode/src/components/ui/date-picker.test.tsx +++ b/packages/vitnode/src/components/ui/date-picker.test.tsx @@ -1,4 +1,3 @@ -// @vitest-environment jsdom import { fireEvent, render, screen } from "@testing-library/react"; import React from "react"; import { describe, expect, it } from "vitest"; diff --git a/packages/vitnode/src/components/ui/date-range-picker.test.tsx b/packages/vitnode/src/components/ui/date-range-picker.test.tsx index a7f3cfe944..5d7ab8c66f 100644 --- a/packages/vitnode/src/components/ui/date-range-picker.test.tsx +++ b/packages/vitnode/src/components/ui/date-range-picker.test.tsx @@ -1,4 +1,3 @@ -// @vitest-environment jsdom import { fireEvent, render, screen } from "@testing-library/react"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; diff --git a/packages/vitnode/src/components/ui/dialog.test.tsx b/packages/vitnode/src/components/ui/dialog.test.tsx index eae71930e9..016441d1e1 100644 --- a/packages/vitnode/src/components/ui/dialog.test.tsx +++ b/packages/vitnode/src/components/ui/dialog.test.tsx @@ -1,4 +1,3 @@ -// @vitest-environment jsdom import { fireEvent, render, screen, waitFor } from "@testing-library/react"; import React from "react"; import { describe, expect, it } from "vitest"; diff --git a/packages/vitnode/src/components/ui/dynamic-icon.client.test.tsx b/packages/vitnode/src/components/ui/dynamic-icon.client.test.tsx index 1d5a839a59..50fb20a27d 100644 --- a/packages/vitnode/src/components/ui/dynamic-icon.client.test.tsx +++ b/packages/vitnode/src/components/ui/dynamic-icon.client.test.tsx @@ -1,4 +1,3 @@ -// @vitest-environment jsdom import { render, waitFor } from "@testing-library/react"; import React from "react"; import { expect, test } from "vitest"; diff --git a/packages/vitnode/src/components/ui/input-group.test.tsx b/packages/vitnode/src/components/ui/input-group.test.tsx deleted file mode 100644 index 2480609def..0000000000 --- a/packages/vitnode/src/components/ui/input-group.test.tsx +++ /dev/null @@ -1,60 +0,0 @@ -// @vitest-environment jsdom -import { fireEvent, render, screen } from "@testing-library/react"; -import { describe, expect, it } from "vitest"; - -import { - InputGroup, - InputGroupAddon, - InputGroupButton, - InputGroupInput, - InputGroupTextarea, -} from "./input-group"; - -describe("InputGroupAddon", () => { - it("focuses the input when the addon is clicked", () => { - render( - - - Find - , - ); - - fireEvent.click(screen.getByText("Find")); - - expect(document.activeElement).toBe( - screen.getByRole("textbox", { name: "Search" }), - ); - }); - - it("focuses the textarea when the addon is clicked", () => { - render( - - - Markdown supported - , - ); - - fireEvent.click(screen.getByText("Markdown supported")); - - expect(document.activeElement).toBe( - screen.getByRole("textbox", { name: "Message" }), - ); - }); - - it("leaves focus alone when a button inside the addon is clicked", () => { - render( - - - - Clear - - , - ); - - fireEvent.click(screen.getByRole("button", { name: "Clear" })); - - expect(document.activeElement).not.toBe( - screen.getByRole("textbox", { name: "Search" }), - ); - }); -}); diff --git a/packages/vitnode/src/components/ui/number-field.test.tsx b/packages/vitnode/src/components/ui/number-field.test.tsx deleted file mode 100644 index 1bba9d8a19..0000000000 --- a/packages/vitnode/src/components/ui/number-field.test.tsx +++ /dev/null @@ -1,137 +0,0 @@ -// @vitest-environment jsdom -import { fireEvent, render, screen } from "@testing-library/react"; -import { describe, expect, it, vi } from "vitest"; - -import { - NumberField, - NumberFieldDecrement, - NumberFieldGroup, - NumberFieldIncrement, - NumberFieldInput, - NumberFieldScrubArea, -} from "./number-field"; - -const renderNumberField = ( - props: React.ComponentProps = {}, -) => - render( - - - - - - - - , - ); - -const input = () => screen.getByLabelText("Amount"); -const increase = () => - screen.getByRole("button", { name: "core.global.increase" }); -const decrease = () => - screen.getByRole("button", { name: "core.global.decrease" }); - -const pressButton = (button: HTMLElement) => { - fireEvent.pointerDown(button, { button: 0, pointerType: "mouse" }); - fireEvent.pointerUp(button, { button: 0, pointerType: "mouse" }); - fireEvent.click(button, { detail: 1 }); -}; - -const changedValues = (onValueChange: ReturnType) => - onValueChange.mock.calls.map(([value]) => value as null | number); - -describe("NumberField", () => { - it("steps up and down by the step with the buttons", () => { - const onValueChange = vi.fn(); - renderNumberField({ defaultValue: 10, onValueChange, step: 5 }); - - pressButton(increase()); - expect(input().value).toBe("15"); - - pressButton(decrease()); - pressButton(decrease()); - expect(input().value).toBe("5"); - expect(changedValues(onValueChange)).toEqual([15, 10, 5]); - }); - - it("clamps at min and max and disables the button at the limit", () => { - const onValueChange = vi.fn(); - renderNumberField({ defaultValue: 9, max: 10, min: 8, onValueChange }); - - pressButton(increase()); - expect(input().value).toBe("10"); - expect(increase().hasAttribute("disabled")).toBe(true); - - pressButton(decrease()); - pressButton(decrease()); - expect(input().value).toBe("8"); - expect(decrease().hasAttribute("disabled")).toBe(true); - expect(changedValues(onValueChange)).toEqual([10, 9, 8]); - }); - - it("steps with the arrow keys and takes the large step with Shift", () => { - renderNumberField({ defaultValue: 0, max: 25 }); - - fireEvent.keyDown(input(), { key: "ArrowUp" }); - expect(input().value).toBe("1"); - - fireEvent.keyDown(input(), { key: "ArrowUp", shiftKey: true }); - expect(input().value).toBe("11"); - - fireEvent.keyDown(input(), { key: "ArrowUp", shiftKey: true }); - fireEvent.keyDown(input(), { key: "ArrowUp", shiftKey: true }); - expect(input().value).toBe("25"); - - fireEvent.keyDown(input(), { key: "ArrowDown" }); - expect(input().value).toBe("24"); - }); - - it("commits a typed value on blur, clamped to the range", () => { - const onValueCommitted = vi.fn(); - renderNumberField({ defaultValue: 1, max: 100, onValueCommitted }); - - fireEvent.focus(input()); - fireEvent.change(input(), { target: { value: "250" } }); - expect(onValueCommitted).not.toHaveBeenCalled(); - - fireEvent.blur(input()); - expect(onValueCommitted).toHaveBeenLastCalledWith(100, expect.anything()); - expect(input().value).toBe("100"); - }); - - it("ignores the buttons and the keyboard when disabled", () => { - const onValueChange = vi.fn(); - renderNumberField({ defaultValue: 3, disabled: true, onValueChange }); - - expect(input().disabled).toBe(true); - expect(increase().hasAttribute("disabled")).toBe(true); - expect(decrease().hasAttribute("disabled")).toBe(true); - - pressButton(increase()); - fireEvent.keyDown(input(), { key: "ArrowUp" }); - expect(onValueChange).not.toHaveBeenCalled(); - expect(input().value).toBe("3"); - }); - - it("keeps a read-only value but leaves the input focusable", () => { - const onValueChange = vi.fn(); - renderNumberField({ defaultValue: 3, onValueChange, readOnly: true }); - - expect(input().readOnly).toBe(true); - expect(increase().getAttribute("aria-disabled")).toBe("true"); - - pressButton(increase()); - fireEvent.keyDown(input(), { key: "ArrowUp" }); - expect(onValueChange).not.toHaveBeenCalled(); - }); - - it("formats the value with the given Intl options", () => { - renderNumberField({ - defaultValue: 1234.5, - format: { currency: "USD", style: "currency" }, - locale: "en-US", - }); - - expect(input().value).toBe("$1,234.50"); - }); -}); diff --git a/packages/vitnode/src/components/ui/pagination.test.tsx b/packages/vitnode/src/components/ui/pagination.test.tsx deleted file mode 100644 index 6e5edff95a..0000000000 --- a/packages/vitnode/src/components/ui/pagination.test.tsx +++ /dev/null @@ -1,108 +0,0 @@ -// @vitest-environment jsdom -import { render, screen } from "@testing-library/react"; -import { describe, expect, it } from "vitest"; - -import { - Pagination, - PaginationContent, - PaginationEllipsis, - PaginationItem, - PaginationLink, - PaginationNext, - PaginationPrevious, -} from "./pagination"; - -const renderPagination = () => - render( - - - - - - {[1, 2, 3].map(page => ( - - - {page} - - - ))} - - - - - , - ); - -describe("Pagination", () => { - it("names every page link by its number", () => { - renderPagination(); - - expect(screen.getByRole("link", { name: "1" }).getAttribute("href")).toBe( - "?page=1", - ); - expect(screen.getByRole("link", { name: "2" })).toBeTruthy(); - expect(screen.getByRole("link", { name: "3" })).toBeTruthy(); - expect(screen.queryByRole("button")).toBeNull(); - }); - - it("marks only the active page as current", () => { - renderPagination(); - - expect( - screen.getByRole("link", { name: "1" }).getAttribute("aria-current"), - ).toBe("page"); - expect( - screen.getByRole("link", { name: "2" }).hasAttribute("aria-current"), - ).toBe(false); - }); - - it("labels previous and next with translated text by default", () => { - renderPagination(); - - const next = screen.getByRole("link", { - name: "core.global.next_page", - }); - const previous = screen.getByRole("link", { - name: "core.global.previous_page", - }); - - expect(next.textContent).toBe("core.global.next"); - expect(previous.textContent).toBe("core.global.previous"); - expect(previous.getAttribute("aria-disabled")).toBe("true"); - expect(previous.hasAttribute("href")).toBe(false); - }); - - it("prefers custom previous and next text", () => { - render( - <> - - - , - ); - - expect( - screen.getByRole("link", { name: "core.global.previous_page" }) - .textContent, - ).toBe("Newer posts"); - expect( - screen.getByRole("link", { name: "core.global.next_page" }).textContent, - ).toBe("Older posts"); - }); - - it("names the navigation landmark with translated text", () => { - renderPagination(); - - expect( - screen.getByRole("navigation", { name: "core.global.pagination" }), - ).toBeTruthy(); - }); - - it("announces skipped pages to screen readers", () => { - render(); - - expect(screen.getByText("core.global.more_pages")).toBeTruthy(); - expect( - screen.getByText("core.global.more_pages").closest("[aria-hidden]"), - ).toBeNull(); - }); -}); diff --git a/packages/vitnode/src/components/ui/progress.test.tsx b/packages/vitnode/src/components/ui/progress.test.tsx deleted file mode 100644 index bc4731c87c..0000000000 --- a/packages/vitnode/src/components/ui/progress.test.tsx +++ /dev/null @@ -1,44 +0,0 @@ -// @vitest-environment jsdom -import { render, screen } from "@testing-library/react"; -import { describe, expect, it } from "vitest"; - -import { Progress, ProgressLabel, ProgressValue } from "./progress"; - -describe("Progress", () => { - it("names the bar with its label and shows the value", () => { - render( - - Uploading - - , - ); - - const bar = screen.getByRole("progressbar", { name: "Uploading" }); - - expect(bar.getAttribute("aria-valuenow")).toBe("42"); - expect(bar.querySelector("[data-slot=progress-value]")?.textContent).toBe( - "42%", - ); - }); - - it("still renders a plain bar without a label", () => { - render(); - - expect(screen.getByRole("progressbar").getAttribute("aria-valuenow")).toBe( - "70", - ); - }); - - it("reports an unknown amount when the value is null", () => { - render( - - Preparing - , - ); - - const bar = screen.getByRole("progressbar", { name: "Preparing" }); - - expect(bar.hasAttribute("aria-valuenow")).toBe(false); - expect(bar.hasAttribute("data-indeterminate")).toBe(true); - }); -}); diff --git a/packages/vitnode/src/components/ui/sidebar.test.tsx b/packages/vitnode/src/components/ui/sidebar.test.tsx index d81a4ddf68..b7dc8df922 100644 --- a/packages/vitnode/src/components/ui/sidebar.test.tsx +++ b/packages/vitnode/src/components/ui/sidebar.test.tsx @@ -1,4 +1,3 @@ -// @vitest-environment jsdom import { fireEvent, render, screen } from "@testing-library/react"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; diff --git a/packages/vitnode/src/components/ui/spinner.test.tsx b/packages/vitnode/src/components/ui/spinner.test.tsx deleted file mode 100644 index e1d4379d91..0000000000 --- a/packages/vitnode/src/components/ui/spinner.test.tsx +++ /dev/null @@ -1,27 +0,0 @@ -// @vitest-environment jsdom -import { render, screen } from "@testing-library/react"; -import { describe, expect, it } from "vitest"; - -import { Spinner } from "./spinner"; - -describe("Spinner", () => { - it("is labelled 'Loading' in the user's language by default", () => { - render(); - - expect( - screen.getByRole("status", { name: "core.global.loading" }), - ).toBeTruthy(); - }); - - it("keeps a custom label", () => { - render(); - - expect(screen.getByRole("status", { name: "Searching" })).toBeTruthy(); - }); - - it("can be hidden from assistive tech next to text that says it all", () => { - render(