@antelopejs/dms 0.8.2 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/dist/implementations/dms/frontend-backend-proxies.d.ts +3 -0
- package/dist/implementations/dms/frontend-backend-proxies.js +39 -0
- package/dist/implementations/dms/frontend-backend-proxies.js.map +1 -0
- package/dist/implementations/dms/frontend-bootstrap.d.ts +1 -0
- package/dist/implementations/dms/frontend-bootstrap.js +13 -0
- package/dist/implementations/dms/frontend-bootstrap.js.map +1 -1
- package/dist/implementations/dms/frontend-modules.d.ts +4 -2
- package/dist/implementations/dms/frontend-modules.js +8 -7
- package/dist/implementations/dms/frontend-modules.js.map +1 -1
- package/dist/implementations/dms/manifest.d.ts +6 -0
- package/dist/implementations/dms/manifest.js +13 -0
- package/dist/implementations/dms/manifest.js.map +1 -1
- package/dist/implementations/dms/menu-categories.d.ts +16 -0
- package/dist/implementations/dms/menu-categories.js +30 -0
- package/dist/implementations/dms/menu-categories.js.map +1 -0
- package/dist/implementations/dms/nav-badges.d.ts +0 -8
- package/dist/implementations/dms/nav-badges.js +0 -16
- package/dist/implementations/dms/nav-badges.js.map +1 -1
- package/dist/implementations/dms/page.d.ts +11 -5
- package/dist/implementations/dms/page.js +56 -41
- package/dist/implementations/dms/page.js.map +1 -1
- package/dist/implementations/dms-html-render/index.js +5 -2
- package/dist/implementations/dms-html-render/index.js.map +1 -1
- package/dist/implementations/dms-notifications/index.d.ts +4 -3
- package/dist/implementations/dms-notifications/index.js +14 -6
- package/dist/implementations/dms-notifications/index.js.map +1 -1
- package/dist/pages/settings/settings-index.js +3 -2
- package/dist/pages/settings/settings-index.js.map +1 -1
- package/dist/pages/settings/users/invites.js +2 -2
- package/dist/pages/settings/users/invites.js.map +1 -1
- package/dist/pages/settings/users/member-change-notifications.js +17 -6
- package/dist/pages/settings/users/member-change-notifications.js.map +1 -1
- package/dist/pages/settings/users/member-invite-form.d.ts +3 -2
- package/dist/pages/settings/users/member-invite-form.js +7 -4
- package/dist/pages/settings/users/member-invite-form.js.map +1 -1
- package/dist/pages/settings/users/member-roles-field.js +2 -1
- package/dist/pages/settings/users/member-roles-field.js.map +1 -1
- package/dist/pages/settings/users/members.js +4 -1
- package/dist/pages/settings/users/members.js.map +1 -1
- package/dist/pages/settings/users/notifications.d.ts +6 -0
- package/dist/pages/settings/users/notifications.js +23 -15
- package/dist/pages/settings/users/notifications.js.map +1 -1
- package/dist/pages/settings/users/profile.js +1 -1
- package/dist/pages/settings/users/profile.js.map +1 -1
- package/dist/pages/settings/users/region-form.d.ts +1 -1
- package/dist/pages/settings/users/region-form.js +2 -1
- package/dist/pages/settings/users/region-form.js.map +1 -1
- package/dist/pages/settings/users/role-editor-store.d.ts +3 -1
- package/dist/pages/settings/users/role-editor-store.js +17 -4
- package/dist/pages/settings/users/role-editor-store.js.map +1 -1
- package/dist/pages/settings/users/roles.js +1 -0
- package/dist/pages/settings/users/roles.js.map +1 -1
- package/dist/realtime/registry.d.ts +1 -0
- package/dist/realtime/registry.js +5 -0
- package/dist/realtime/registry.js.map +1 -1
- package/dist/routes/realtime.d.ts +5 -1
- package/dist/routes/realtime.js +16 -9
- package/dist/routes/realtime.js.map +1 -1
- package/dist/test/integration/account-deletion.test.js +2 -2
- package/dist/test/integration/account-deletion.test.js.map +1 -1
- package/dist/test/integration/auth/auth-key-rotation.test.js +2 -1
- package/dist/test/integration/auth/auth-key-rotation.test.js.map +1 -1
- package/dist/test/integration/auth/current-password.test.js +2 -1
- package/dist/test/integration/auth/current-password.test.js.map +1 -1
- package/dist/test/integration/auth/session-revocation.test.js +2 -1
- package/dist/test/integration/auth/session-revocation.test.js.map +1 -1
- package/dist/test/integration/auth/sign-in-country.test.js +2 -1
- package/dist/test/integration/auth/sign-in-country.test.js.map +1 -1
- package/dist/test/integration/auth/two-factor-codes.test.js +2 -1
- package/dist/test/integration/auth/two-factor-codes.test.js.map +1 -1
- package/dist/test/integration/auth/two-factor-password.test.js +3 -2
- package/dist/test/integration/auth/two-factor-password.test.js.map +1 -1
- package/dist/test/integration/email-change.test.js +2 -1
- package/dist/test/integration/email-change.test.js.map +1 -1
- package/dist/test/integration/files/component-files.test.js +2 -2
- package/dist/test/integration/files/component-files.test.js.map +1 -1
- package/dist/test/integration/member-settings-access.test.js +8 -7
- package/dist/test/integration/member-settings-access.test.js.map +1 -1
- package/dist/test/integration/menu-access-events.test.d.ts +1 -0
- package/dist/test/integration/menu-access-events.test.js +148 -0
- package/dist/test/integration/menu-access-events.test.js.map +1 -0
- package/dist/test/integration/notification-inbox.test.js +4 -1
- package/dist/test/integration/notification-inbox.test.js.map +1 -1
- package/dist/test/integration/notification-locked-subjects.test.js +3 -2
- package/dist/test/integration/notification-locked-subjects.test.js.map +1 -1
- package/dist/test/integration/notification-unread-badge.test.js +51 -5
- package/dist/test/integration/notification-unread-badge.test.js.map +1 -1
- package/dist/test/integration/page-layout-route-guard.test.d.ts +1 -0
- package/dist/test/integration/page-layout-route-guard.test.js +56 -0
- package/dist/test/integration/page-layout-route-guard.test.js.map +1 -0
- package/dist/test/integration/permission-warnings.test.js +3 -2
- package/dist/test/integration/permission-warnings.test.js.map +1 -1
- package/dist/test/integration/removed-member-notifications.test.js +3 -2
- package/dist/test/integration/removed-member-notifications.test.js.map +1 -1
- package/dist/test/integration/role-delete-reassign.test.js +2 -1
- package/dist/test/integration/role-delete-reassign.test.js.map +1 -1
- package/dist/test/integration/role-permission-ancestors.test.js +2 -1
- package/dist/test/integration/role-permission-ancestors.test.js.map +1 -1
- package/dist/test/unit/frontend-bootstrap.test.js +13 -0
- package/dist/test/unit/frontend-bootstrap.test.js.map +1 -1
- package/dist/test/unit/implementations/dms/category-slug-registry.test.js +14 -5
- package/dist/test/unit/implementations/dms/category-slug-registry.test.js.map +1 -1
- package/dist/test/unit/implementations/dms/frontend-backend-proxies.test.d.ts +1 -0
- package/dist/test/unit/implementations/dms/frontend-backend-proxies.test.js +66 -0
- package/dist/test/unit/implementations/dms/frontend-backend-proxies.test.js.map +1 -0
- package/dist/test/unit/implementations/dms/manifest-strip.test.js +26 -0
- package/dist/test/unit/implementations/dms/manifest-strip.test.js.map +1 -1
- package/dist/test/unit/implementations/dms/nav-badges.test.js +23 -9
- package/dist/test/unit/implementations/dms/nav-badges.test.js.map +1 -1
- package/dist/test/unit/implementations/dms-html-render/generate-html.test.js +22 -4
- package/dist/test/unit/implementations/dms-html-render/generate-html.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms/page-layout-query.test.js +12 -0
- package/dist/test/unit/interfaces/dms/page-layout-query.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms/page-teardown.test.js +1 -1
- package/dist/test/unit/interfaces/dms/page-teardown.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms-base/table-and-card-catalog.test.js +25 -0
- package/dist/test/unit/interfaces/dms-base/table-and-card-catalog.test.js.map +1 -1
- package/dist/test/unit/pages/settings/member-roles-field.test.js +1 -1
- package/dist/test/unit/pages/settings/member-roles-field.test.js.map +1 -1
- package/dist/test/unit/pages/settings/role-delete-confirm.test.js.map +1 -1
- package/dist/test/unit/realtime/menu-topics.test.d.ts +1 -0
- package/dist/test/unit/realtime/menu-topics.test.js +26 -0
- package/dist/test/unit/realtime/menu-topics.test.js.map +1 -0
- package/dist/validation/regional-preferences.schema.d.ts +2 -2
- package/frontend-vue/deferred-plugins.ts +3 -4
- package/frontend-vue/dms.email.ts +5 -1
- package/frontend-vue/dms.frontend.ts +15 -5
- package/frontend-vue/dms.server.ts +74 -0
- package/frontend-vue/layers/dms-auth/app/composables/useLogout.ts +1 -1
- package/frontend-vue/layers/dms-auth/app/composables/usePostLoginRedirect.ts +16 -3
- package/frontend-vue/layers/dms-auth/app/custom-pages/auth/2fa.vue +3 -1
- package/frontend-vue/layers/dms-auth/app/custom-pages/auth/backup.vue +3 -1
- package/frontend-vue/layers/dms-auth/app/custom-pages/auth/forgot-validation.vue +1 -1
- package/frontend-vue/layers/dms-auth/app/custom-pages/auth/oauth-complete.vue +26 -3
- package/frontend-vue/layers/dms-auth/app/custom-pages/auth/recover.vue +1 -1
- package/frontend-vue/layers/dms-auth/app/custom-pages/auth/signup.vue +22 -25
- package/frontend-vue/layers/dms-auth/app/custom-pages/auth/validate.vue +1 -1
- package/frontend-vue/layers/dms-auth/app/middleware/auth.ts +5 -7
- package/frontend-vue/layers/dms-auth/app/plugins/permissions-preload.ts +17 -0
- package/frontend-vue/layers/dms-auth/server/accounts.ts +135 -0
- package/frontend-vue/layers/dms-auth/server/actions.ts +251 -0
- package/frontend-vue/layers/dms-auth/server/oauth.ts +260 -0
- package/frontend-vue/layers/dms-auth/server/session.ts +158 -0
- package/frontend-vue/layers/dms-core/app/build/utils/asyncDataScope.ts +47 -0
- package/frontend-vue/layers/dms-core/app/build/utils/permission-preview.ts +6 -2
- package/frontend-vue/layers/dms-core/app/composables/auth/useApiData.ts +148 -0
- package/frontend-vue/layers/dms-core/app/composables/auth/useAuthFetch.ts +7 -20
- package/frontend-vue/layers/dms-core/app/composables/auth/useCurrentUser.ts +2 -2
- package/frontend-vue/layers/dms-core/app/composables/auth/useMultiAccount.ts +18 -5
- package/frontend-vue/layers/dms-core/app/composables/auth/usePermissions.ts +35 -6
- package/frontend-vue/layers/dms-core/app/composables/auth/useSessionRecovery.ts +1 -1
- package/frontend-vue/layers/dms-core/app/composables/period/compareResolvers.ts +20 -14
- package/frontend-vue/layers/dms-core/app/composables/period/periodClock.ts +121 -0
- package/frontend-vue/layers/dms-core/app/composables/period/periodRenderNow.ts +42 -0
- package/frontend-vue/layers/dms-core/app/composables/period/periodTimeZone.ts +31 -0
- package/frontend-vue/layers/dms-core/app/composables/period/presetResolvers.ts +48 -19
- package/frontend-vue/layers/dms-core/app/composables/period/usePeriod.ts +33 -4
- package/frontend-vue/layers/dms-core/app/composables/period/usePeriodScope.ts +78 -25
- package/frontend-vue/layers/dms-core/app/composables/realtime/usePageRealtime.ts +3 -0
- package/frontend-vue/layers/dms-core/app/composables/realtime/useUserRealtime.ts +36 -6
- package/frontend-vue/layers/dms-core/app/composables/user/useUserRegionalPreferences.ts +1 -1
- package/frontend-vue/layers/dms-core/app/plugins/async-data-scope.ts +26 -0
- package/frontend-vue/layers/dms-core/app/utils/pageRoutes.ts +22 -0
- package/frontend-vue/layers/dms-core/app/utils/regional.ts +36 -3
- package/frontend-vue/layers/dms-core/app/utils/trailingDeduper.ts +10 -1
- package/frontend-vue/layers/dms-core/shared/types/app-config.ts +48 -4
- package/frontend-vue/layers/dms-layout/app/app.config.ts +42 -23
- package/frontend-vue/layers/dms-layout/app/assets/css/main.css +72 -20
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardFrame.vue +9 -0
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardSidebar.vue +0 -3
- package/frontend-vue/layers/dms-layout/app/build/components/notification/NotificationPopover.vue +9 -3
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/members/MemberRolePicker.vue +13 -12
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationInboxDisplay.vue +17 -7
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationPreferencesForm.vue +0 -1
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/profile/ProfilePersonalInfo.vue +36 -33
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/profile/ProfileSecuritySummary.vue +1 -2
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/role-types.ts +5 -4
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecuritySessions.vue +35 -28
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecurityStatus.vue +0 -1
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecurityTwoFactor.vue +1 -1
- package/frontend-vue/layers/dms-layout/app/build/composables/dev-reload/useDevReloadHolder.ts +7 -6
- package/frontend-vue/layers/dms-layout/app/build/composables/layout/sidePanelState.ts +1 -1
- package/frontend-vue/layers/dms-layout/app/build/composables/navigation/useVisitNavBadges.ts +26 -0
- package/frontend-vue/layers/dms-layout/app/build/composables/notification/useNotificationCatalog.ts +25 -23
- package/frontend-vue/layers/dms-layout/app/build/composables/notification/useNotificationPreferences.ts +44 -23
- package/frontend-vue/layers/dms-layout/app/build/composables/security/useSessionHandoff.ts +1 -1
- package/frontend-vue/layers/dms-layout/app/build/composables/settings/profile/preferencesSummary.ts +1 -1
- package/frontend-vue/layers/dms-layout/app/build/composables/settings/profile/useAccountData.ts +3 -2
- package/frontend-vue/layers/dms-layout/app/build/composables/settings/profile/useProfileAvatar.ts +1 -18
- package/frontend-vue/layers/dms-layout/app/build/composables/settings/profile/useProfilePreferences.ts +19 -23
- package/frontend-vue/layers/dms-layout/app/build/composables/settings/security/useSecurityFormat.ts +6 -3
- package/frontend-vue/layers/dms-layout/app/build/composables/settings/security/useSecurityOverview.ts +13 -18
- package/frontend-vue/layers/dms-layout/app/build/utils/modules-catalog.ts +8 -6
- package/frontend-vue/layers/dms-layout/app/build/utils/publicErrorMessage.ts +19 -0
- package/frontend-vue/layers/dms-layout/app/components/navigation/CollapsedMenuItem.vue +0 -11
- package/frontend-vue/layers/dms-layout/app/components/navigation/NavigationMenu.vue +3 -13
- package/frontend-vue/layers/dms-layout/app/composables/general/useSystemState.ts +7 -11
- package/frontend-vue/layers/dms-layout/app/composables/notification/useNotifications.ts +103 -22
- package/frontend-vue/layers/dms-layout/app/composables/page/useModulesListing.ts +25 -102
- package/frontend-vue/layers/dms-layout/app/composables/page/usePageComponentPreload.ts +0 -10
- package/frontend-vue/layers/dms-layout/app/composables/page/useSiteLayout.ts +177 -96
- package/frontend-vue/layers/dms-layout/app/composables/profile/useUserLanguage.ts +3 -2
- package/frontend-vue/layers/dms-layout/app/custom-pages/modules.vue +6 -28
- package/frontend-vue/layers/dms-layout/app/emails/components/EmailLayout.vue +31 -11
- package/frontend-vue/layers/dms-layout/app/error.vue +5 -2
- package/frontend-vue/layers/dms-layout/app/middleware/module-routing.global.ts +1 -1
- package/frontend-vue/layers/dms-layout/app/middleware/page-leave-guard.global.ts +2 -2
- package/frontend-vue/layers/dms-layout/app/pages/[...slug].vue +12 -9
- package/frontend-vue/layers/dms-layout/app/plugins/dms-menu-sync.client.ts +31 -3
- package/frontend-vue/layers/dms-layout/app/plugins/favicon.ts +58 -0
- package/frontend-vue/layers/dms-layout/app/plugins/notification-stream.client.ts +15 -7
- package/frontend-vue/layers/dms-layout/app/plugins/profile-sync.client.ts +5 -1
- package/frontend-vue/layers/dms-layout/app/plugins/regional-preferences.ts +10 -4
- package/frontend-vue/layers/dms-layout/app/plugins/time-zone-cookie.client.ts +12 -0
- package/frontend-vue/layers/dms-layout/app/types/page.ts +13 -0
- package/frontend-vue/layers/dms-layout/server/html-render.ts +58 -0
- package/frontend-vue/layers/dms-layout/server/render-token.ts +95 -0
- package/frontend-vue/layers/dms-onboarding/app/composables/onboarding/useOnboarding.ts +25 -21
- package/frontend-vue/layers/dms-onboarding/app/middleware/onboarding.global.ts +1 -1
- package/frontend-vue/layers/dms-onboarding/app/plugins/onboarding-preload.ts +9 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerDefaults.ts +4 -1
- package/frontend-vue/layers/dms-ui/app/build/composables/files/useFileMetadata.ts +162 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formRecordLoad.ts +23 -4
- package/frontend-vue/layers/dms-ui/app/build/composables/live/usePageDataRefresh.ts +101 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/navigation/useNavBadges.ts +33 -6
- package/frontend-vue/layers/dms-ui/app/build/composables/period/periodDisplay.ts +19 -16
- package/frontend-vue/layers/dms-ui/app/build/composables/period/periodQuery.ts +40 -20
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableFooter.ts +6 -8
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableListData.ts +42 -14
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableRows.ts +19 -8
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViews.ts +1 -1
- package/frontend-vue/layers/dms-ui/app/components/chart/ChartCard.vue +5 -2
- package/frontend-vue/layers/dms-ui/app/components/flow-canvas/FlowCanvas.vue +9 -9
- package/frontend-vue/layers/dms-ui/app/components/form/Form.vue +20 -18
- package/frontend-vue/layers/dms-ui/app/components/form/components/Cascader.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/components/form/components/File.vue +26 -132
- package/frontend-vue/layers/dms-ui/app/components/form/components/Image.vue +40 -105
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputAddress.vue +2 -0
- package/frontend-vue/layers/dms-ui/app/components/form/components/Relation.vue +4 -4
- package/frontend-vue/layers/dms-ui/app/components/period/PeriodSelector.vue +9 -13
- package/frontend-vue/layers/dms-ui/app/components/table-view/CascaderPath.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/components/table-view/FilePreview.vue +12 -24
- package/frontend-vue/layers/dms-ui/app/components/table-view/ImagePreview.vue +17 -29
- package/frontend-vue/layers/dms-ui/app/components/table-view/KanbanBoard.vue +7 -3
- package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +20 -8
- package/frontend-vue/layers/dms-ui/app/components/top-list/TopListCard.vue +8 -11
- package/frontend-vue/layers/dms-ui/app/composables/chart/useChartFetch.ts +146 -182
- package/frontend-vue/layers/dms-ui/app/composables/live/usePageLiveRefresh.ts +8 -2
- package/frontend-vue/layers/dms-ui/app/composables/useFileReadUrls.ts +11 -99
- package/frontend-vue/layers/dms-ui/app/utils/blockRefresh.ts +32 -3
- package/frontend-vue/package.json +2 -2
- package/frontend-vue/pnpm-lock.yaml +5 -5
- package/frontend-vue/server/private-options.ts +27 -0
- package/frontend-vue/tests/async-data-scope.test.ts +121 -0
- package/frontend-vue/tests/auth-middleware-error.test.ts +3 -3
- package/frontend-vue/tests/banner-block-fetch.test.ts +3 -0
- package/frontend-vue/tests/block-fetch-options-forwarding.test.ts +3 -0
- package/frontend-vue/tests/block-items-period-realtime.test.ts +3 -0
- package/frontend-vue/tests/block-not-found.test.ts +16 -2
- package/frontend-vue/tests/block-page-refresh.test.ts +3 -0
- package/frontend-vue/tests/block-route-params.test.ts +3 -0
- package/frontend-vue/tests/branding-defaults.test.ts +34 -0
- package/frontend-vue/tests/builtin-emails.test.ts +23 -2
- package/frontend-vue/tests/chart-live-pause.test.ts +4 -1
- package/frontend-vue/tests/chart-value-propagation.test.ts +4 -1
- package/frontend-vue/tests/composed-text-blocks.test.ts +3 -0
- package/frontend-vue/tests/email-layout.test.ts +66 -4
- package/frontend-vue/tests/error-page-message.test.ts +25 -2
- package/frontend-vue/tests/favicon.test.ts +131 -0
- package/frontend-vue/tests/file-fields-ssr.test.ts +145 -0
- package/frontend-vue/tests/file-read-urls.test.ts +145 -34
- package/frontend-vue/tests/file-unavailable.test.ts +5 -1
- package/frontend-vue/tests/fixtures/dataEngine.ts +44 -0
- package/frontend-vue/tests/form-load-error.test.ts +55 -1
- package/frontend-vue/tests/helpers/api-data-runtime.ts +77 -0
- package/frontend-vue/tests/image-hydration-ids.test.ts +21 -16
- package/frontend-vue/tests/image-reference-preservation.test.ts +59 -53
- package/frontend-vue/tests/input-number-adjust.test.ts +14 -0
- package/frontend-vue/tests/key-value-list-copy.test.ts +3 -0
- package/frontend-vue/tests/member-role-picker.test.ts +4 -2
- package/frontend-vue/tests/menu-sync-reconnect.test.ts +47 -0
- package/frontend-vue/tests/metadata-retry.test.ts +5 -1
- package/frontend-vue/tests/module-routing.test.ts +2 -5
- package/frontend-vue/tests/modules-catalog.test.ts +17 -4
- package/frontend-vue/tests/modules-listing-transfer.test.ts +95 -0
- package/frontend-vue/tests/multi-account-validate.test.ts +23 -1
- package/frontend-vue/tests/nav-badge-tones.test.ts +66 -3
- package/frontend-vue/tests/notification-counts-from-writes.test.ts +223 -0
- package/frontend-vue/tests/notification-counts-transfer.test.ts +88 -0
- package/frontend-vue/tests/notification-inbox-display.test.ts +57 -5
- package/frontend-vue/tests/notification-popover-errors.test.ts +21 -1
- package/frontend-vue/tests/oauth-complete-handoff.test.ts +209 -0
- package/frontend-vue/tests/onboarding-middleware.test.ts +1 -1
- package/frontend-vue/tests/onboarding-transfer.test.ts +106 -0
- package/frontend-vue/tests/page-data-refresh.test.ts +141 -0
- package/frontend-vue/tests/page-fill-height.test.ts +16 -3
- package/frontend-vue/tests/page-no-visible-blocks.test.ts +12 -2
- package/frontend-vue/tests/page-realtime-remount.test.ts +1 -1
- package/frontend-vue/tests/page-record-label.test.ts +0 -7
- package/frontend-vue/tests/page-validation-redirect.test.ts +12 -2
- package/frontend-vue/tests/period-hydration-now.test.ts +185 -0
- package/frontend-vue/tests/period-live-pause.test.ts +3 -0
- package/frontend-vue/tests/period-reader-zone.test.ts +137 -0
- package/frontend-vue/tests/period-scope-first-fetch.test.ts +17 -1
- package/frontend-vue/tests/period-ssr.test.ts +195 -0
- package/frontend-vue/tests/permission-preview-partial.test.ts +0 -2
- package/frontend-vue/tests/permissions-transfer.test.ts +194 -0
- package/frontend-vue/tests/post-login-redirect.test.ts +10 -5
- package/frontend-vue/tests/profile-personal-info-ssr.test.ts +150 -0
- package/frontend-vue/tests/profile-preferences-summary.test.ts +15 -11
- package/frontend-vue/tests/realtime-reconnect.test.ts +22 -3
- package/frontend-vue/tests/roles-name-errors.test.ts +1 -1
- package/frontend-vue/tests/row-side-panel.test.ts +1 -1
- package/frontend-vue/tests/security-sessions-load-error.test.ts +3 -1
- package/frontend-vue/tests/server-routes-vs-pages.test.ts +26 -0
- package/frontend-vue/tests/server-routes.test.ts +582 -0
- package/frontend-vue/tests/session-handoff.test.ts +1 -1
- package/frontend-vue/tests/settings-reads-ssr.test.ts +232 -0
- package/frontend-vue/tests/side-panels.test.ts +8 -1
- package/frontend-vue/tests/sign-in-exit-history.test.ts +219 -0
- package/frontend-vue/tests/signup-invitation-link.test.ts +56 -8
- package/frontend-vue/tests/site-layout-categories.test.ts +42 -0
- package/frontend-vue/tests/site-layout-hydration.test.ts +3 -3
- package/frontend-vue/tests/site-layout-rendered.test.ts +103 -0
- package/frontend-vue/tests/site-layout-resync.test.ts +87 -0
- package/frontend-vue/tests/ssr-guard-preload.test.ts +188 -0
- package/frontend-vue/tests/table-cell-tones.test.ts +36 -1
- package/frontend-vue/tests/table-data-ssr.test.ts +195 -0
- package/frontend-vue/tests/table-list-data-requests.test.ts +154 -0
- package/frontend-vue/tests/table-retry-loading.test.ts +37 -0
- package/frontend-vue/tests/table-source-navigation.test.ts +0 -1
- package/frontend-vue/tests/table-source-period-scope.test.ts +0 -1
- package/frontend-vue/tests/table-view-views.test.ts +1 -1
- package/frontend-vue/tests/top-list-card-actions.test.ts +3 -0
- package/frontend-vue/tests/top-list-card-period-badge.test.ts +79 -0
- package/frontend-vue/tests/top-list-card-value-suffix.test.ts +3 -0
- package/frontend-vue/tests/trailing-deduper.test.ts +22 -0
- package/frontend-vue/tests/use-api-data-update.test.ts +119 -0
- package/frontend-vue/tests/use-api-data.test.ts +410 -0
- package/frontend-vue/tests/user-language.test.ts +8 -5
- package/frontend-vue/tests/widget-label-i18n.test.ts +4 -1
- package/frontend-vue/vitest.config.ts +6 -0
- package/package.json +2 -2
- package/skills/dms/SKILL.md +2 -1
- package/skills/dms-module/SKILL.md +1 -1
- package/skills/dms-pages/SKILL.md +4 -1
- package/frontend-vue/layers/dms-core/app/build/composables/page/usePageRecordLabel.ts +0 -9
- package/frontend-vue/layers/dms-layout/app/composables/page/usePrefetch.ts +0 -21
- package/frontend-vue/layers/dms-ui/app/build/composables/form/useControlError.ts +0 -10
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useServerRenderedAsyncData.ts +0 -23
- package/frontend-vue/tests/control-error-path.test.ts +0 -11
- package/frontend-vue/tests/modules-listing-per-request.test.ts +0 -104
- package/frontend-vue/tests/page-layout-background-refresh.test.ts +0 -321
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
/**
|
|
3
|
+
* The reads of the settings pages through the runtime's real async data: a
|
|
4
|
+
* server render reads what the page shows and hands it to the browser, which
|
|
5
|
+
* hydrates without asking again; the blocks of a page showing one request
|
|
6
|
+
* share it, and a change still reads it again.
|
|
7
|
+
*/
|
|
8
|
+
import {
|
|
9
|
+
computed,
|
|
10
|
+
defineComponent,
|
|
11
|
+
h,
|
|
12
|
+
nextTick,
|
|
13
|
+
ref,
|
|
14
|
+
watch,
|
|
15
|
+
type App,
|
|
16
|
+
type Component,
|
|
17
|
+
} from "vue";
|
|
18
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
19
|
+
import { hydrate, serverRender, stubApiData } from "./helpers/api-data-runtime";
|
|
20
|
+
import { useSecurityOverview } from "../layers/dms-layout/app/build/composables/settings/security/useSecurityOverview";
|
|
21
|
+
import { useNotificationPreferences } from "../layers/dms-layout/app/build/composables/notification/useNotificationPreferences";
|
|
22
|
+
import { useNotificationCatalog } from "../layers/dms-layout/app/build/composables/notification/useNotificationCatalog";
|
|
23
|
+
|
|
24
|
+
vi.mock(
|
|
25
|
+
"../layers/dms-layout/app/build/composables/security/useSessionHandoff",
|
|
26
|
+
() => ({ useSessionHandoff: () => async () => {} }),
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const NOW = new Date("2026-10-15T12:00:00Z").getTime();
|
|
30
|
+
const MINUTE = 60_000;
|
|
31
|
+
|
|
32
|
+
const ANSWERS: Record<string, unknown> = {
|
|
33
|
+
"/dms/pages/settings/user/security": {
|
|
34
|
+
activeSessions: 2,
|
|
35
|
+
attention: ["two_factor_off"],
|
|
36
|
+
attentionTone: "warning",
|
|
37
|
+
attentionTones: { two_factor_off: "warning" },
|
|
38
|
+
},
|
|
39
|
+
"/dms/pages/settings/user/security/sessions": [
|
|
40
|
+
{
|
|
41
|
+
_id: "other",
|
|
42
|
+
browser: "Firefox",
|
|
43
|
+
os: "Linux",
|
|
44
|
+
// Half a minute short of 5: rounded to 4 minutes, to 5 a second later.
|
|
45
|
+
lastActiveAt: new Date(NOW - 4.5 * MINUTE + 1000).toISOString(),
|
|
46
|
+
isCurrent: false,
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
"/dms/pages/settings/user/notifications/categories": {
|
|
50
|
+
categories: [{ id: "core", labelKey: "Core", icon: "i-ph-bell" }],
|
|
51
|
+
subjects: [
|
|
52
|
+
{ id: "login", labelKey: "Login", category: { id: "core" } },
|
|
53
|
+
{ id: "mention", labelKey: "Mention", category: { id: "core" } },
|
|
54
|
+
],
|
|
55
|
+
},
|
|
56
|
+
"/dms/pages/settings/user/notifications/preferences": { "core:mention": false },
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const authFetch = vi.fn(async (url: string, options?: { method?: string }) => {
|
|
60
|
+
if (options?.method === "PATCH") return undefined;
|
|
61
|
+
return structuredClone(ANSWERS[url]);
|
|
62
|
+
});
|
|
63
|
+
const reads = () =>
|
|
64
|
+
authFetch.mock.calls.filter(([, options]) => !options?.method).map(([url]) => url);
|
|
65
|
+
const mounted: App[] = [];
|
|
66
|
+
|
|
67
|
+
/** Renders `root` on the server, then hydrates it: the reads of each side. */
|
|
68
|
+
async function renderAndHydrate(root: Component) {
|
|
69
|
+
vi.stubEnv("SSR", true);
|
|
70
|
+
const { html, payload } = await serverRender(root);
|
|
71
|
+
vi.stubEnv("SSR", false);
|
|
72
|
+
const server = reads();
|
|
73
|
+
authFetch.mockClear();
|
|
74
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
75
|
+
const { app, container } = hydrate(root, html, payload);
|
|
76
|
+
mounted.push(app);
|
|
77
|
+
await nextTick();
|
|
78
|
+
await new Promise((done) => setTimeout(done, 0));
|
|
79
|
+
const hydrationWarnings = warn.mock.calls
|
|
80
|
+
.flat()
|
|
81
|
+
.filter((message) => /hydration/i.test(String(message)));
|
|
82
|
+
warn.mockRestore();
|
|
83
|
+
return { html, container, server, browser: reads(), hydrationWarnings };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
beforeEach(() => {
|
|
87
|
+
vi.useFakeTimers({ toFake: ["Date"] });
|
|
88
|
+
vi.setSystemTime(NOW);
|
|
89
|
+
vi.stubGlobal("computed", computed);
|
|
90
|
+
vi.stubGlobal("ref", ref);
|
|
91
|
+
vi.stubGlobal("watch", watch);
|
|
92
|
+
vi.stubGlobal("useI18n", () => ({
|
|
93
|
+
locale: ref("en"),
|
|
94
|
+
t: (key: string) => key,
|
|
95
|
+
te: () => false,
|
|
96
|
+
}));
|
|
97
|
+
stubApiData(authFetch as never);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
afterEach(() => {
|
|
101
|
+
mounted.splice(0).forEach((app) => app.unmount());
|
|
102
|
+
vi.useRealTimers();
|
|
103
|
+
vi.unstubAllEnvs();
|
|
104
|
+
vi.unstubAllGlobals();
|
|
105
|
+
authFetch.mockClear();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe("Security", () => {
|
|
109
|
+
it("renders the summary every block shares from one read, and the browser does not read it again", async () => {
|
|
110
|
+
const Block = defineComponent({
|
|
111
|
+
setup() {
|
|
112
|
+
const { overview, attention } = useSecurityOverview();
|
|
113
|
+
return () =>
|
|
114
|
+
h("p", `${overview.value?.activeSessions}:${attention.value.join()}`);
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
const Page = defineComponent({
|
|
118
|
+
setup: () => () => h("div", [h(Block), h(Block)]),
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const { html, server, browser, hydrationWarnings } =
|
|
122
|
+
await renderAndHydrate(Page);
|
|
123
|
+
expect(html).toBe("<div><p>2:two_factor_off</p><p>2:two_factor_off</p></div>");
|
|
124
|
+
expect(server).toEqual(["/dms/pages/settings/user/security"]);
|
|
125
|
+
expect(browser).toEqual([]);
|
|
126
|
+
expect(hydrationWarnings).toEqual([]);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("says when a session was last active as of the read, the same in the hydrating browser", async () => {
|
|
130
|
+
const { default: SecuritySessions } = await import(
|
|
131
|
+
"../layers/dms-layout/app/build/components/pages/settings/security/SecuritySessions.vue"
|
|
132
|
+
);
|
|
133
|
+
vi.stubGlobal("useI18n", () => ({
|
|
134
|
+
locale: ref("en"),
|
|
135
|
+
t: (key: string, named?: Record<string, unknown>) =>
|
|
136
|
+
named ? `${key}(${Object.values(named).join()})` : key,
|
|
137
|
+
te: () => false,
|
|
138
|
+
}));
|
|
139
|
+
vi.stubGlobal("useToast", () => ({ add: vi.fn() }));
|
|
140
|
+
const slotted = (tag: string) =>
|
|
141
|
+
defineComponent({
|
|
142
|
+
setup:
|
|
143
|
+
(_, { slots }) =>
|
|
144
|
+
() =>
|
|
145
|
+
h(tag, [slots.badge?.(), slots.default?.(), slots.meta?.()]),
|
|
146
|
+
});
|
|
147
|
+
const register = (app: App) => {
|
|
148
|
+
for (const name of ["DmsSection", "DmsListRow"])
|
|
149
|
+
app.component(name, slotted("div"));
|
|
150
|
+
for (const name of [
|
|
151
|
+
"DmsEmptyState",
|
|
152
|
+
"USkeleton",
|
|
153
|
+
"UButton",
|
|
154
|
+
"UBadge",
|
|
155
|
+
"DmsStatusPill",
|
|
156
|
+
"UModal",
|
|
157
|
+
])
|
|
158
|
+
app.component(name, () => null);
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
vi.stubEnv("SSR", true);
|
|
162
|
+
const { html, payload } = await serverRender(SecuritySessions, register);
|
|
163
|
+
vi.stubEnv("SSR", false);
|
|
164
|
+
expect(html).toContain("active_ago(4 minutes ago)");
|
|
165
|
+
// The browser hydrates two seconds later: "5 minutes ago" from now.
|
|
166
|
+
vi.setSystemTime(NOW + 2000);
|
|
167
|
+
authFetch.mockClear();
|
|
168
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
169
|
+
const { app, container } = hydrate(SecuritySessions, html, payload, register);
|
|
170
|
+
mounted.push(app);
|
|
171
|
+
await nextTick();
|
|
172
|
+
await new Promise((done) => setTimeout(done, 0));
|
|
173
|
+
expect(reads()).toEqual([]);
|
|
174
|
+
expect(container.innerHTML).toBe(html);
|
|
175
|
+
expect(warn.mock.calls.flat().join(" ")).not.toMatch(/hydration/i);
|
|
176
|
+
warn.mockRestore();
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
describe("Notification preferences", () => {
|
|
181
|
+
it("renders the preferences and the catalog the inbox shares, read once on the server", async () => {
|
|
182
|
+
const Preferences = defineComponent({
|
|
183
|
+
setup() {
|
|
184
|
+
const { isLoading, isEnabled, categories, subjectsOf } =
|
|
185
|
+
useNotificationPreferences();
|
|
186
|
+
return () =>
|
|
187
|
+
h(
|
|
188
|
+
"ul",
|
|
189
|
+
isLoading.value
|
|
190
|
+
? "loading"
|
|
191
|
+
: categories.value.flatMap((category) =>
|
|
192
|
+
subjectsOf(category.id).map((subject) =>
|
|
193
|
+
h("li", `${subject.id}:${isEnabled(subject)}`),
|
|
194
|
+
),
|
|
195
|
+
),
|
|
196
|
+
);
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
const Inbox = defineComponent({
|
|
200
|
+
setup() {
|
|
201
|
+
const { findCategory } = useNotificationCatalog();
|
|
202
|
+
return () => h("p", findCategory("core")?.labelKey ?? "-");
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
const Page = defineComponent({
|
|
206
|
+
setup: () => () => h("div", [h(Preferences), h(Inbox)]),
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
const { html, server, browser, hydrationWarnings } =
|
|
210
|
+
await renderAndHydrate(Page);
|
|
211
|
+
expect(html).toBe(
|
|
212
|
+
"<div><ul><li>login:true</li><li>mention:false</li></ul><p>Core</p></div>",
|
|
213
|
+
);
|
|
214
|
+
expect(server.sort()).toEqual([
|
|
215
|
+
"/dms/pages/settings/user/notifications/categories",
|
|
216
|
+
"/dms/pages/settings/user/notifications/preferences",
|
|
217
|
+
]);
|
|
218
|
+
expect(browser).toEqual([]);
|
|
219
|
+
expect(hydrationWarnings).toEqual([]);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it("reads nothing while the page does not show them", async () => {
|
|
223
|
+
const Hidden = defineComponent({
|
|
224
|
+
setup() {
|
|
225
|
+
const { isLoading } = useNotificationPreferences(() => false);
|
|
226
|
+
return () => h("p", String(isLoading.value));
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
await serverRender(Hidden);
|
|
230
|
+
expect(reads()).toEqual([]);
|
|
231
|
+
});
|
|
232
|
+
});
|
|
@@ -40,6 +40,10 @@ import {
|
|
|
40
40
|
providePageFillHeight,
|
|
41
41
|
} from "../layers/dms-layout/app/composables/layout/usePageFillHeight";
|
|
42
42
|
|
|
43
|
+
vi.mock(
|
|
44
|
+
"../layers/dms-layout/app/build/composables/navigation/useVisitNavBadges",
|
|
45
|
+
() => ({ useVisitNavBadges: () => {} }),
|
|
46
|
+
);
|
|
43
47
|
vi.mock(
|
|
44
48
|
"../layers/dms-layout/app/build/components/layout/DashboardSidebar.vue",
|
|
45
49
|
() => ({ default: () => null }),
|
|
@@ -136,7 +140,10 @@ function installRuntime(): void {
|
|
|
136
140
|
useDmsState,
|
|
137
141
|
useI18n: () => ({ t: (key: string) => key }),
|
|
138
142
|
useTranslation: () => ({ processI18n: (value: string) => value }),
|
|
139
|
-
|
|
143
|
+
useDmsUserSession: () => ({ loggedIn }),
|
|
144
|
+
// The page brought its site layout.
|
|
145
|
+
useRenderedSiteLayout: () => {},
|
|
146
|
+
useRenderedBellCounts: () => {},
|
|
140
147
|
};
|
|
141
148
|
Object.entries(runtime).forEach(([name, value]) =>
|
|
142
149
|
vi.stubGlobal(name, value),
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { afterEach, beforeEach, expect, it, vi } from "vitest";
|
|
3
|
+
import {
|
|
4
|
+
computed,
|
|
5
|
+
createApp,
|
|
6
|
+
defineComponent,
|
|
7
|
+
h,
|
|
8
|
+
nextTick,
|
|
9
|
+
onMounted,
|
|
10
|
+
reactive,
|
|
11
|
+
ref,
|
|
12
|
+
watch,
|
|
13
|
+
watchEffect,
|
|
14
|
+
type App,
|
|
15
|
+
type Component,
|
|
16
|
+
} from "vue";
|
|
17
|
+
import { usePostLoginRedirect } from "../layers/dms-auth/app/composables/usePostLoginRedirect";
|
|
18
|
+
|
|
19
|
+
// The pages that end a sign-in by spending a one-time token in their URL: the
|
|
20
|
+
// OAuth completion and the second factor (code or backup code). Each leaves
|
|
21
|
+
// for the destination in its own place in the history, so Back never lands on
|
|
22
|
+
// a spent token, or on a completion page that sends the user forward again.
|
|
23
|
+
|
|
24
|
+
vi.mock("#dms-ui/app/build/components/form/OtpInput.vue", async () => {
|
|
25
|
+
const { defineComponent: define, h: render } = await import("vue");
|
|
26
|
+
return {
|
|
27
|
+
default: define({
|
|
28
|
+
props: { modelValue: Array },
|
|
29
|
+
emits: ["update:modelValue"],
|
|
30
|
+
setup: (_, { emit, expose }) => {
|
|
31
|
+
expose({ focus: () => undefined });
|
|
32
|
+
return () =>
|
|
33
|
+
render("input", {
|
|
34
|
+
"data-testid": "code",
|
|
35
|
+
onInput: (event: Event) =>
|
|
36
|
+
emit(
|
|
37
|
+
"update:modelValue",
|
|
38
|
+
(event.target as HTMLInputElement).value.split(""),
|
|
39
|
+
),
|
|
40
|
+
});
|
|
41
|
+
},
|
|
42
|
+
}),
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
const stub = vi.hoisted(() => async () => {
|
|
46
|
+
const { defineComponent: define, h: render } = await import("vue");
|
|
47
|
+
return {
|
|
48
|
+
default: define({
|
|
49
|
+
setup:
|
|
50
|
+
(_: unknown, { slots }: { slots: Record<string, () => unknown> }) =>
|
|
51
|
+
() =>
|
|
52
|
+
render("section", slots.default?.() as never),
|
|
53
|
+
}),
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
vi.mock("../layers/dms-layout/app/build/components/layout/StageCard.vue", stub);
|
|
57
|
+
vi.mock("../layers/dms-auth/app/build/components/AuthBackLink.vue", stub);
|
|
58
|
+
vi.mock("../layers/dms-auth/app/build/components/AuthFormAlert.vue", stub);
|
|
59
|
+
|
|
60
|
+
const { default: OAuthCompletePage } = await import(
|
|
61
|
+
"../layers/dms-auth/app/custom-pages/auth/oauth-complete.vue"
|
|
62
|
+
);
|
|
63
|
+
const { default: TwoFactorPage } = await import(
|
|
64
|
+
"../layers/dms-auth/app/custom-pages/auth/2fa.vue"
|
|
65
|
+
);
|
|
66
|
+
const { default: BackupCodePage } = await import(
|
|
67
|
+
"../layers/dms-auth/app/custom-pages/auth/backup.vue"
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const HOMEPAGE = "/home";
|
|
71
|
+
const navigate = vi.fn();
|
|
72
|
+
const fetchApi = vi.fn();
|
|
73
|
+
let route: { path: string; query: Record<string, string> };
|
|
74
|
+
let app: App;
|
|
75
|
+
let host: HTMLDivElement;
|
|
76
|
+
|
|
77
|
+
const Form = defineComponent({
|
|
78
|
+
props: { state: { type: Object, required: true } },
|
|
79
|
+
emits: ["submit"],
|
|
80
|
+
setup: (props, { emit, expose, slots }) => {
|
|
81
|
+
expose({ setErrors: () => undefined, getErrors: () => [] });
|
|
82
|
+
return () =>
|
|
83
|
+
h(
|
|
84
|
+
"form",
|
|
85
|
+
{
|
|
86
|
+
onSubmit: (event: Event) => {
|
|
87
|
+
event.preventDefault();
|
|
88
|
+
emit("submit", { data: props.state });
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
slots.default?.(),
|
|
92
|
+
);
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
const Passthrough = defineComponent({
|
|
96
|
+
setup:
|
|
97
|
+
(_, { slots }) =>
|
|
98
|
+
() =>
|
|
99
|
+
h("span", slots.default?.()),
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
function installRuntime() {
|
|
103
|
+
Object.entries({
|
|
104
|
+
computed,
|
|
105
|
+
ref,
|
|
106
|
+
reactive,
|
|
107
|
+
watch,
|
|
108
|
+
watchEffect,
|
|
109
|
+
onMounted,
|
|
110
|
+
}).forEach(([key, value]) => vi.stubGlobal(key, value));
|
|
111
|
+
vi.stubGlobal("useI18n", () => ({ t: (key: string) => key }));
|
|
112
|
+
vi.stubGlobal("useTranslation", () => ({
|
|
113
|
+
processApiMessage: (message: string) => message,
|
|
114
|
+
}));
|
|
115
|
+
vi.stubGlobal("useToast", () => ({ add: vi.fn() }));
|
|
116
|
+
vi.stubGlobal("useDmsRoute", () => route);
|
|
117
|
+
vi.stubGlobal("useHomepage", () => HOMEPAGE);
|
|
118
|
+
vi.stubGlobal("useDmsApp", () => ({
|
|
119
|
+
runWithContext: (fn: () => unknown) => fn(),
|
|
120
|
+
$i18n: { setLocale: vi.fn() },
|
|
121
|
+
}));
|
|
122
|
+
vi.stubGlobal("useDmsUserSession", () => ({
|
|
123
|
+
fetch: async () => undefined,
|
|
124
|
+
user: ref({ _id: "user" }),
|
|
125
|
+
}));
|
|
126
|
+
vi.stubGlobal("useMultiAccount", () => ({ addCurrentAccount: vi.fn() }));
|
|
127
|
+
vi.stubGlobal("useSiteLayout", () => ({ refresh: async () => undefined }));
|
|
128
|
+
vi.stubGlobal("withAccountsFlag", (_query: unknown, extra = {}) => extra);
|
|
129
|
+
vi.stubGlobal("usePostLoginRedirect", usePostLoginRedirect);
|
|
130
|
+
vi.stubGlobal("navigateDms", navigate);
|
|
131
|
+
vi.stubGlobal("$fetch", fetchApi);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function mountPage(page: Component) {
|
|
135
|
+
app = createApp(page);
|
|
136
|
+
app.config.globalProperties.$t = (key: string) => key;
|
|
137
|
+
for (const name of ["UButton", "UFormField", "UInput", "UIcon", "DmsLink"])
|
|
138
|
+
app.component(name, Passthrough);
|
|
139
|
+
app.component("UForm", Form);
|
|
140
|
+
app.mount(host);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function settle() {
|
|
144
|
+
for (let tick = 0; tick < 10; tick++) {
|
|
145
|
+
await Promise.resolve();
|
|
146
|
+
await nextTick();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function submit(code: string) {
|
|
151
|
+
const input = host.querySelector<HTMLInputElement>('[data-testid="code"]');
|
|
152
|
+
if (input) {
|
|
153
|
+
input.value = code;
|
|
154
|
+
input.dispatchEvent(new Event("input"));
|
|
155
|
+
await settle();
|
|
156
|
+
}
|
|
157
|
+
host.querySelector("form")!.dispatchEvent(new Event("submit"));
|
|
158
|
+
await settle();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
beforeEach(() => {
|
|
162
|
+
navigate.mockReset();
|
|
163
|
+
fetchApi.mockReset();
|
|
164
|
+
installRuntime();
|
|
165
|
+
host = document.createElement("div");
|
|
166
|
+
document.body.append(host);
|
|
167
|
+
});
|
|
168
|
+
afterEach(() => {
|
|
169
|
+
app?.unmount();
|
|
170
|
+
host.remove();
|
|
171
|
+
vi.unstubAllGlobals();
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("replaces the OAuth completion page with the destination", async () => {
|
|
175
|
+
route = { path: "/auth/oauth/complete", query: { redirect: "/settings" } };
|
|
176
|
+
fetchApi.mockResolvedValueOnce({ kind: "none" });
|
|
177
|
+
mountPage(OAuthCompletePage);
|
|
178
|
+
await settle();
|
|
179
|
+
|
|
180
|
+
expect(fetchApi).toHaveBeenCalledWith("/auth/oauth/handoff", {
|
|
181
|
+
method: "POST",
|
|
182
|
+
});
|
|
183
|
+
expect(navigate).toHaveBeenCalledTimes(1);
|
|
184
|
+
expect(navigate).toHaveBeenCalledWith("/settings", { replace: true });
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("replaces the second-factor page with the destination", async () => {
|
|
188
|
+
route = {
|
|
189
|
+
path: "/auth/2fa",
|
|
190
|
+
query: { token: "spent-token", methods: "totp" },
|
|
191
|
+
};
|
|
192
|
+
fetchApi.mockResolvedValueOnce(undefined);
|
|
193
|
+
mountPage(TwoFactorPage);
|
|
194
|
+
await settle();
|
|
195
|
+
await submit("123456");
|
|
196
|
+
|
|
197
|
+
expect(fetchApi).toHaveBeenCalledWith("/auth/verify-2fa", {
|
|
198
|
+
method: "POST",
|
|
199
|
+
body: { token: "spent-token", code: "123456", method: "totp" },
|
|
200
|
+
});
|
|
201
|
+
expect(navigate).toHaveBeenCalledWith(HOMEPAGE, { replace: true });
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("replaces the backup code page with the destination", async () => {
|
|
205
|
+
route = { path: "/auth/backup", query: { token: "spent-token" } };
|
|
206
|
+
fetchApi.mockResolvedValueOnce(undefined);
|
|
207
|
+
mountPage(BackupCodePage);
|
|
208
|
+
await settle();
|
|
209
|
+
(host.querySelector("form") as HTMLFormElement).dispatchEvent(
|
|
210
|
+
new Event("submit"),
|
|
211
|
+
);
|
|
212
|
+
await settle();
|
|
213
|
+
|
|
214
|
+
expect(fetchApi).toHaveBeenCalledWith(
|
|
215
|
+
"/auth/verify-2fa",
|
|
216
|
+
expect.objectContaining({ method: "POST" }),
|
|
217
|
+
);
|
|
218
|
+
expect(navigate).toHaveBeenCalledWith(HOMEPAGE, { replace: true });
|
|
219
|
+
});
|
|
@@ -13,6 +13,12 @@ import {
|
|
|
13
13
|
} from "vue";
|
|
14
14
|
import * as z from "zod";
|
|
15
15
|
import SignupPage from "../layers/dms-auth/app/custom-pages/auth/signup.vue";
|
|
16
|
+
import {
|
|
17
|
+
browserRuntime,
|
|
18
|
+
hydrate,
|
|
19
|
+
serverRender,
|
|
20
|
+
stubApiData,
|
|
21
|
+
} from "./helpers/api-data-runtime";
|
|
16
22
|
|
|
17
23
|
const route = reactive<{ query: Record<string, unknown> }>({ query: {} });
|
|
18
24
|
let app: App;
|
|
@@ -54,7 +60,7 @@ function installRuntime() {
|
|
|
54
60
|
runWithContext: (fn: () => unknown) => fn(),
|
|
55
61
|
}));
|
|
56
62
|
vi.stubGlobal("useDmsRoute", () => route);
|
|
57
|
-
|
|
63
|
+
stubApiData(authFetch);
|
|
58
64
|
vi.stubGlobal("passwordSchema", z.string());
|
|
59
65
|
vi.stubGlobal("usePasswordStrength", () => ({
|
|
60
66
|
strength: ref([]),
|
|
@@ -66,6 +72,7 @@ function installRuntime() {
|
|
|
66
72
|
function mountSignup(query: Record<string, unknown>) {
|
|
67
73
|
route.query = query;
|
|
68
74
|
app = createApp(SignupPage);
|
|
75
|
+
browserRuntime(app);
|
|
69
76
|
app.config.globalProperties.$t = (key: string) => key;
|
|
70
77
|
for (const name of [
|
|
71
78
|
"DmsCard",
|
|
@@ -83,10 +90,10 @@ function mountSignup(query: Record<string, unknown>) {
|
|
|
83
90
|
app.mount(host);
|
|
84
91
|
}
|
|
85
92
|
|
|
86
|
-
/** Let the invitation check the page starts
|
|
93
|
+
/** Let the invitation check the page starts settle. */
|
|
87
94
|
async function settle() {
|
|
88
|
-
await
|
|
89
|
-
await
|
|
95
|
+
for (let tick = 0; tick < 4; tick++) await nextTick();
|
|
96
|
+
await new Promise((done) => setTimeout(done, 0));
|
|
90
97
|
}
|
|
91
98
|
|
|
92
99
|
function refusal(code: string) {
|
|
@@ -132,10 +139,13 @@ it("shows the signup form once the server accepts the invitation", async () => {
|
|
|
132
139
|
|
|
133
140
|
await settle();
|
|
134
141
|
|
|
135
|
-
expect(authFetch).toHaveBeenCalledWith(
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
142
|
+
expect(authFetch).toHaveBeenCalledWith(
|
|
143
|
+
"/api/auth/validate-invite-token",
|
|
144
|
+
expect.objectContaining({
|
|
145
|
+
method: "POST",
|
|
146
|
+
body: { token: "invite-token", email: "new@local.test" },
|
|
147
|
+
}),
|
|
148
|
+
);
|
|
139
149
|
expect(host.querySelector('[data-testid="signup-invalid-invitation"]')).toBe(
|
|
140
150
|
null,
|
|
141
151
|
);
|
|
@@ -197,3 +207,41 @@ it.each([{}, { lang: "en" }, { lang: "de" }, { lang: ["fr", "en"] }])(
|
|
|
197
207
|
expect(locale.value).toBe("en");
|
|
198
208
|
},
|
|
199
209
|
);
|
|
210
|
+
|
|
211
|
+
// Checked with the page: the server renders the notice, and the browser
|
|
212
|
+
// hydrates it without asking again (no form flashing first).
|
|
213
|
+
it("renders a refused invitation's notice on the server, and the browser does not check it again", async () => {
|
|
214
|
+
authFetch.mockRejectedValue(refusal("error.invite_revoked"));
|
|
215
|
+
route.query = { token: "stale-token", email: "new@local.test" };
|
|
216
|
+
const register = (application: App) => {
|
|
217
|
+
application.config.globalProperties.$t = (key: string) => key;
|
|
218
|
+
for (const name of [
|
|
219
|
+
"DmsCard",
|
|
220
|
+
"UIcon",
|
|
221
|
+
"UForm",
|
|
222
|
+
"UFormField",
|
|
223
|
+
"UInput",
|
|
224
|
+
"DmsOAuthButtons",
|
|
225
|
+
"DmsPasswordStrength",
|
|
226
|
+
"DmsLink",
|
|
227
|
+
])
|
|
228
|
+
application.component(name, Passthrough);
|
|
229
|
+
application.component("UButton", LinkButton);
|
|
230
|
+
};
|
|
231
|
+
vi.stubEnv("SSR", true);
|
|
232
|
+
const { html, payload } = await serverRender(SignupPage, register);
|
|
233
|
+
vi.stubEnv("SSR", false);
|
|
234
|
+
expect(html).toContain("page.signup.invitation_revoked_title");
|
|
235
|
+
expect(html).not.toContain("page.signup.submit");
|
|
236
|
+
expect(authFetch).toHaveBeenCalledTimes(1);
|
|
237
|
+
|
|
238
|
+
authFetch.mockClear();
|
|
239
|
+
const hydrated = hydrate(SignupPage, html, payload, register);
|
|
240
|
+
app = hydrated.app;
|
|
241
|
+
await settle();
|
|
242
|
+
expect(authFetch).not.toHaveBeenCalled();
|
|
243
|
+
expect(hydrated.container.textContent).toContain(
|
|
244
|
+
"page.signup.invitation_revoked_title",
|
|
245
|
+
);
|
|
246
|
+
vi.unstubAllEnvs();
|
|
247
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { findNavigableCategory } from "../layers/dms-layout/app/composables/page/useSiteLayout";
|
|
3
|
+
import type { SiteLayout } from "../layers/dms-layout/app/types/page";
|
|
4
|
+
|
|
5
|
+
// The site layout lists every category once, by full id, and its pages name
|
|
6
|
+
// theirs by id. A URL resolves against the categories that own a slug: a
|
|
7
|
+
// `urlTransparent` one answers at its parent's, and is never a URL's target.
|
|
8
|
+
|
|
9
|
+
const layout = {
|
|
10
|
+
pages: {},
|
|
11
|
+
categories: {
|
|
12
|
+
projects: {
|
|
13
|
+
id: "projects",
|
|
14
|
+
fullId: "projects",
|
|
15
|
+
fullSlug: "/projects",
|
|
16
|
+
displayName: "Projects",
|
|
17
|
+
},
|
|
18
|
+
"projects.reports": {
|
|
19
|
+
id: "reports",
|
|
20
|
+
fullId: "projects.reports",
|
|
21
|
+
fullSlug: "/projects",
|
|
22
|
+
displayName: "Reports",
|
|
23
|
+
categoryId: "projects",
|
|
24
|
+
urlTransparent: true,
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
} as unknown as SiteLayout;
|
|
28
|
+
|
|
29
|
+
describe("the category a URL resolves to", () => {
|
|
30
|
+
it("is the one owning the slug, by its full slug", () => {
|
|
31
|
+
expect(findNavigableCategory(layout, "/projects")?.fullId).toBe("projects");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("is never a transparent category, nor an unknown slug", () => {
|
|
35
|
+
expect(findNavigableCategory(layout, "/reports")).toBeUndefined();
|
|
36
|
+
const transparentOnly = {
|
|
37
|
+
pages: {},
|
|
38
|
+
categories: { "projects.reports": layout.categories["projects.reports"] },
|
|
39
|
+
} as unknown as SiteLayout;
|
|
40
|
+
expect(findNavigableCategory(transparentOnly, "/projects")).toBeUndefined();
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -70,13 +70,13 @@ it("does not show a stale request error after successful Inertia navigation", as
|
|
|
70
70
|
fail = reject;
|
|
71
71
|
}),
|
|
72
72
|
);
|
|
73
|
-
const
|
|
74
|
-
vi.stubGlobal("
|
|
73
|
+
const showDmsError = vi.fn();
|
|
74
|
+
vi.stubGlobal("showDmsError", showDmsError);
|
|
75
75
|
const layout = useSiteLayout();
|
|
76
76
|
const request = layout.loadSiteLayout();
|
|
77
77
|
state.get("dms-siteLayout")!.value = { pages: { fresh: {} }, categories: {} };
|
|
78
78
|
fail(new Error("old account request"));
|
|
79
79
|
await expect(request).rejects.toThrow("old account request");
|
|
80
|
-
expect(
|
|
80
|
+
expect(showDmsError).not.toHaveBeenCalled();
|
|
81
81
|
expect(layout.loadingError.value).toBeNull();
|
|
82
82
|
});
|