@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
|
@@ -4,11 +4,13 @@ import {
|
|
|
4
4
|
createApp,
|
|
5
5
|
defineComponent,
|
|
6
6
|
h,
|
|
7
|
+
nextTick,
|
|
7
8
|
onMounted,
|
|
8
9
|
ref,
|
|
9
10
|
watch,
|
|
10
11
|
type App,
|
|
11
12
|
type Component,
|
|
13
|
+
type Ref,
|
|
12
14
|
} from "vue";
|
|
13
15
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
14
16
|
import NotificationInboxDisplay from "../layers/dms-layout/app/build/components/pages/settings/notification/NotificationInboxDisplay.vue";
|
|
@@ -19,16 +21,20 @@ const notifications = vi.hoisted(() => ({
|
|
|
19
21
|
deleteNotification: vi.fn(async () => {}),
|
|
20
22
|
}));
|
|
21
23
|
|
|
24
|
+
// The bell's state the display watches, shared like `useDmsState`.
|
|
25
|
+
const bell = vi.hoisted(() => ({
|
|
26
|
+
unreadCount: undefined as unknown as Ref<number>,
|
|
27
|
+
notifications: undefined as unknown as Ref<{ _id: string }[]>,
|
|
28
|
+
}));
|
|
29
|
+
|
|
22
30
|
vi.mock(
|
|
23
31
|
"../layers/dms-layout/app/composables/notification/useNotifications",
|
|
24
32
|
async () => {
|
|
25
33
|
const vue = await import("vue");
|
|
34
|
+
bell.unreadCount = vue.ref(0);
|
|
35
|
+
bell.notifications = vue.ref([]);
|
|
26
36
|
return {
|
|
27
|
-
useNotifications: () => ({
|
|
28
|
-
...notifications,
|
|
29
|
-
unreadCount: vue.ref(0),
|
|
30
|
-
notifications: vue.ref([]),
|
|
31
|
-
}),
|
|
37
|
+
useNotifications: () => ({ ...notifications, ...bell }),
|
|
32
38
|
};
|
|
33
39
|
},
|
|
34
40
|
);
|
|
@@ -113,6 +119,8 @@ function mount(component: Component, props: Record<string, unknown>) {
|
|
|
113
119
|
}
|
|
114
120
|
|
|
115
121
|
beforeEach(() => {
|
|
122
|
+
bell.unreadCount.value = 0;
|
|
123
|
+
bell.notifications.value = [];
|
|
116
124
|
vi.stubGlobal("computed", computed);
|
|
117
125
|
vi.stubGlobal("watch", watch);
|
|
118
126
|
vi.stubGlobal("onMounted", onMounted);
|
|
@@ -166,4 +174,48 @@ describe("notifications inbox display", () => {
|
|
|
166
174
|
expect(notifications.markAsUnread).toHaveBeenCalledWith("n2");
|
|
167
175
|
expect(notifications.deleteNotification).toHaveBeenCalledWith("n2");
|
|
168
176
|
});
|
|
177
|
+
|
|
178
|
+
it("lists the feed again once per change made here, though the change moves the counts", async () => {
|
|
179
|
+
const refresh = vi.fn(async () => {});
|
|
180
|
+
const container = mount(NotificationInboxDisplay as Component, {
|
|
181
|
+
context: contextOf(refresh),
|
|
182
|
+
});
|
|
183
|
+
// As the bell does: the write's echo, then its answer, move the count and
|
|
184
|
+
// rebuild the bell's lists.
|
|
185
|
+
notifications.markAsRead.mockImplementationOnce(async () => {
|
|
186
|
+
bell.unreadCount.value = 4;
|
|
187
|
+
bell.notifications.value = [{ _id: "n1" }];
|
|
188
|
+
await new Promise((done) => setTimeout(done, 0));
|
|
189
|
+
bell.unreadCount.value = 3;
|
|
190
|
+
bell.notifications.value = [{ _id: "n1" }];
|
|
191
|
+
});
|
|
192
|
+
container.querySelector<HTMLButtonElement>("article .toggle")!.click();
|
|
193
|
+
await vi.waitFor(() => expect(refresh).toHaveBeenCalledTimes(1));
|
|
194
|
+
await new Promise((done) => setTimeout(done, 10));
|
|
195
|
+
expect(refresh).toHaveBeenCalledTimes(1);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("lists the feed again once per change the stream brings, not when the bell rebuilds its lists", async () => {
|
|
199
|
+
const refresh = vi.fn(async () => {});
|
|
200
|
+
mount(NotificationInboxDisplay as Component, {
|
|
201
|
+
context: contextOf(refresh),
|
|
202
|
+
});
|
|
203
|
+
bell.notifications.value = [{ _id: "n1" }, { _id: "n2" }];
|
|
204
|
+
await nextTick();
|
|
205
|
+
refresh.mockClear();
|
|
206
|
+
|
|
207
|
+
// A read state changing elsewhere rebuilds the lists, then the count moves.
|
|
208
|
+
bell.notifications.value = [{ _id: "n1" }, { _id: "n2" }];
|
|
209
|
+
await nextTick();
|
|
210
|
+
expect(refresh).not.toHaveBeenCalled();
|
|
211
|
+
bell.unreadCount.value += 1;
|
|
212
|
+
await nextTick();
|
|
213
|
+
expect(refresh).toHaveBeenCalledTimes(1);
|
|
214
|
+
|
|
215
|
+
// A notification arriving: listed first and counted, together.
|
|
216
|
+
bell.notifications.value = [{ _id: "n0" }, ...bell.notifications.value];
|
|
217
|
+
bell.unreadCount.value += 1;
|
|
218
|
+
await nextTick();
|
|
219
|
+
expect(refresh).toHaveBeenCalledTimes(2);
|
|
220
|
+
});
|
|
169
221
|
});
|
|
@@ -22,6 +22,7 @@ const NOTIFICATIONS_PAGE = "settings.user.notifications";
|
|
|
22
22
|
interface NotificationsStub {
|
|
23
23
|
unreadCount: Ref<number>;
|
|
24
24
|
unreadTone: Ref<string | undefined>;
|
|
25
|
+
areCountsLoaded: Ref<boolean>;
|
|
25
26
|
notifications: Ref<unknown[]>;
|
|
26
27
|
fetchBellCounts: ReturnType<typeof vi.fn>;
|
|
27
28
|
fetchNotifications: ReturnType<typeof vi.fn>;
|
|
@@ -105,7 +106,7 @@ function installRuntime(): void {
|
|
|
105
106
|
return state.get(key);
|
|
106
107
|
});
|
|
107
108
|
vi.stubGlobal("useI18n", () => ({ t: (key: string) => key, locale: "en" }));
|
|
108
|
-
vi.stubGlobal("
|
|
109
|
+
vi.stubGlobal("useDmsUserSession", () => ({ loggedIn: ref(true) }));
|
|
109
110
|
vi.stubGlobal("useTranslation", () => ({
|
|
110
111
|
processI18n: (value: string) => value,
|
|
111
112
|
}));
|
|
@@ -175,6 +176,9 @@ beforeEach(() => {
|
|
|
175
176
|
notifications = {
|
|
176
177
|
unreadCount: ref(0),
|
|
177
178
|
unreadTone: ref(undefined),
|
|
179
|
+
// Not read with the page (signed in since the app started): the bell
|
|
180
|
+
// reads them once mounted.
|
|
181
|
+
areCountsLoaded: ref(false),
|
|
178
182
|
notifications: ref([]),
|
|
179
183
|
fetchBellCounts: vi.fn(),
|
|
180
184
|
fetchNotifications: vi.fn(),
|
|
@@ -298,3 +302,19 @@ it("keeps the page alive when the notification list is refused on open", async (
|
|
|
298
302
|
expect(errorHandler).not.toHaveBeenCalled();
|
|
299
303
|
expect(host.textContent).toContain("notification.dropdown.no_notifications");
|
|
300
304
|
});
|
|
305
|
+
|
|
306
|
+
it("does not read the counts again when the page brought them", async () => {
|
|
307
|
+
notifications.areCountsLoaded.value = true;
|
|
308
|
+
notifications.unreadCount.value = 2;
|
|
309
|
+
app = createApp(NotificationPopover);
|
|
310
|
+
app.config.globalProperties.$t = (key: string) => key;
|
|
311
|
+
for (const name of ["UButton", "USeparator", "UIcon"])
|
|
312
|
+
app.component(name, Passthrough);
|
|
313
|
+
app.component("UPopover", Popover);
|
|
314
|
+
app.component("UCard", Card);
|
|
315
|
+
app.mount(host);
|
|
316
|
+
await nextTick();
|
|
317
|
+
await new Promise((done) => setTimeout(done, 0));
|
|
318
|
+
expect(notifications.fetchBellCounts).not.toHaveBeenCalled();
|
|
319
|
+
expect(host.textContent).toContain("2");
|
|
320
|
+
});
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import {
|
|
3
|
+
computed,
|
|
4
|
+
createApp,
|
|
5
|
+
defineComponent,
|
|
6
|
+
h,
|
|
7
|
+
nextTick,
|
|
8
|
+
onMounted,
|
|
9
|
+
reactive,
|
|
10
|
+
ref,
|
|
11
|
+
Suspense,
|
|
12
|
+
watch,
|
|
13
|
+
type App,
|
|
14
|
+
type Ref,
|
|
15
|
+
} from "vue";
|
|
16
|
+
import { afterEach, beforeEach, expect, it, vi } from "vitest";
|
|
17
|
+
import { usePostLoginRedirect } from "../layers/dms-auth/app/composables/usePostLoginRedirect";
|
|
18
|
+
import { usePageLayout } from "../layers/dms-layout/app/composables/page/usePageLayout";
|
|
19
|
+
import { useSiteLayout } from "../layers/dms-layout/app/composables/page/useSiteLayout";
|
|
20
|
+
import type { PageLayout } from "../layers/dms-layout/app/types/page";
|
|
21
|
+
|
|
22
|
+
// The OAuth completion page, drawn by the real page layout cache: the
|
|
23
|
+
// post-login redirect refreshes the site layout for the new identity, which
|
|
24
|
+
// empties that cache and remounts the page while its layout comes back.
|
|
25
|
+
|
|
26
|
+
const { default: OAuthCompletePage } = await import(
|
|
27
|
+
"../layers/dms-auth/app/custom-pages/auth/oauth-complete.vue"
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const PATH = "/auth/oauth/complete";
|
|
31
|
+
const LAYOUT_URL = `${PATH}/pagelayout`;
|
|
32
|
+
const SITE_LAYOUT_URL = "/dms/sitelayout";
|
|
33
|
+
const HANDOFF_URL = "/auth/oauth/handoff";
|
|
34
|
+
const HOMEPAGE = "/home";
|
|
35
|
+
|
|
36
|
+
const state = new Map<string, Ref>();
|
|
37
|
+
const authFetch = vi.fn();
|
|
38
|
+
const fetchHandoff = vi.fn();
|
|
39
|
+
const navigate = vi.fn();
|
|
40
|
+
let mounts = 0;
|
|
41
|
+
let route: { path: string; query: Record<string, string> };
|
|
42
|
+
let app: App;
|
|
43
|
+
let host: HTMLDivElement;
|
|
44
|
+
|
|
45
|
+
const completionLayout: PageLayout = {
|
|
46
|
+
components: { complete: { options: {} } },
|
|
47
|
+
layout: {},
|
|
48
|
+
} as unknown as PageLayout;
|
|
49
|
+
|
|
50
|
+
const siteResponse = {
|
|
51
|
+
siteLayout: {
|
|
52
|
+
pages: {
|
|
53
|
+
[PATH]: {
|
|
54
|
+
id: "auth-oauth-complete",
|
|
55
|
+
fullId: "auth-oauth-complete",
|
|
56
|
+
fullSlug: PATH,
|
|
57
|
+
displayName: "Completing",
|
|
58
|
+
layoutUrl: LAYOUT_URL,
|
|
59
|
+
publicAccess: true,
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
categories: {},
|
|
63
|
+
},
|
|
64
|
+
siteLayoutTree: undefined,
|
|
65
|
+
quickActions: undefined,
|
|
66
|
+
modules: {},
|
|
67
|
+
isOwner: false,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const Page = defineComponent({
|
|
71
|
+
async setup() {
|
|
72
|
+
const { pageLayout } = await usePageLayout();
|
|
73
|
+
return () =>
|
|
74
|
+
h(
|
|
75
|
+
"main",
|
|
76
|
+
Object.keys(pageLayout.value?.components ?? {}).map((key) =>
|
|
77
|
+
h(OAuthCompletePage, { key }),
|
|
78
|
+
),
|
|
79
|
+
);
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
function installRuntime() {
|
|
84
|
+
const runtime = {
|
|
85
|
+
runWithContext: (fn: () => unknown) => fn(),
|
|
86
|
+
$i18n: { setLocale: vi.fn() },
|
|
87
|
+
};
|
|
88
|
+
Object.entries({ computed, ref, watch }).forEach(([key, value]) =>
|
|
89
|
+
vi.stubGlobal(key, value),
|
|
90
|
+
);
|
|
91
|
+
vi.stubGlobal("onMounted", (hook: () => unknown) =>
|
|
92
|
+
onMounted(() => {
|
|
93
|
+
mounts += 1;
|
|
94
|
+
return hook();
|
|
95
|
+
}),
|
|
96
|
+
);
|
|
97
|
+
vi.stubGlobal("useDmsApp", () => runtime);
|
|
98
|
+
vi.stubGlobal("useDmsRoute", () => route);
|
|
99
|
+
vi.stubGlobal("useDmsState", (key: string, initial: () => unknown) => {
|
|
100
|
+
if (!state.has(key)) state.set(key, ref(initial()));
|
|
101
|
+
return state.get(key);
|
|
102
|
+
});
|
|
103
|
+
vi.stubGlobal("useAuthFetch", () => ({ $authFetch: authFetch }));
|
|
104
|
+
vi.stubGlobal("useSiteLayout", useSiteLayout);
|
|
105
|
+
vi.stubGlobal("useHomepage", () => HOMEPAGE);
|
|
106
|
+
vi.stubGlobal("useDmsUserSession", () => ({
|
|
107
|
+
fetch: async () => undefined,
|
|
108
|
+
user: ref({ _id: "user" }),
|
|
109
|
+
}));
|
|
110
|
+
vi.stubGlobal("useMultiAccount", () => ({ addCurrentAccount: vi.fn() }));
|
|
111
|
+
vi.stubGlobal("usePostLoginRedirect", usePostLoginRedirect);
|
|
112
|
+
vi.stubGlobal("navigateDms", navigate);
|
|
113
|
+
vi.stubGlobal("$fetch", (url: string, options: unknown) =>
|
|
114
|
+
url === HANDOFF_URL ? fetchHandoff(options) : Promise.reject(url),
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function mountCompletionPage() {
|
|
119
|
+
route.path = PATH;
|
|
120
|
+
const layout = useSiteLayout();
|
|
121
|
+
layout.siteLayout.value = siteResponse.siteLayout as never;
|
|
122
|
+
layout.pageLayouts.value = { [LAYOUT_URL]: completionLayout };
|
|
123
|
+
app = createApp({
|
|
124
|
+
render: () => h(Suspense, null, { default: () => h(Page) }),
|
|
125
|
+
});
|
|
126
|
+
app.config.globalProperties.$t = (key: string) => key;
|
|
127
|
+
app.component("UIcon", defineComponent({ setup: () => () => h("i") }));
|
|
128
|
+
app.mount(host);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const siteLayoutFetches = () =>
|
|
132
|
+
authFetch.mock.calls.filter(([url]) => url === SITE_LAYOUT_URL).length;
|
|
133
|
+
|
|
134
|
+
async function settle() {
|
|
135
|
+
for (let tick = 0; tick < 20; tick++) {
|
|
136
|
+
await Promise.resolve();
|
|
137
|
+
await nextTick();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
beforeEach(() => {
|
|
142
|
+
state.clear();
|
|
143
|
+
mounts = 0;
|
|
144
|
+
// Answered on later tasks, like the network, the page layout first as on
|
|
145
|
+
// the playground: the page is drawn again before the redirect leaves it.
|
|
146
|
+
authFetch.mockImplementation(async (url: string) => {
|
|
147
|
+
const site = url === SITE_LAYOUT_URL;
|
|
148
|
+
await new Promise((resolve) => setTimeout(resolve, site ? 5 : 0));
|
|
149
|
+
return site ? siteResponse : completionLayout;
|
|
150
|
+
});
|
|
151
|
+
fetchHandoff.mockResolvedValue({ kind: "none" });
|
|
152
|
+
route = reactive({ path: PATH, query: {} });
|
|
153
|
+
// Leaving the page unmounts it, as the router does: the completion page
|
|
154
|
+
// has no layout on any other route.
|
|
155
|
+
navigate.mockImplementation(async (to: string | { path: string }) => {
|
|
156
|
+
route.path = typeof to === "string" ? to : to.path;
|
|
157
|
+
});
|
|
158
|
+
installRuntime();
|
|
159
|
+
host = document.createElement("div");
|
|
160
|
+
document.body.append(host);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
afterEach(() => {
|
|
164
|
+
app?.unmount();
|
|
165
|
+
host.remove();
|
|
166
|
+
vi.unstubAllGlobals();
|
|
167
|
+
vi.resetAllMocks();
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("consumes the handoff once when the identity refresh remounts the page", async () => {
|
|
171
|
+
mountCompletionPage();
|
|
172
|
+
await vi.waitFor(() =>
|
|
173
|
+
expect(navigate).toHaveBeenCalledWith(HOMEPAGE, { replace: true }),
|
|
174
|
+
);
|
|
175
|
+
await settle();
|
|
176
|
+
|
|
177
|
+
expect(fetchHandoff).toHaveBeenCalledOnce();
|
|
178
|
+
expect(siteLayoutFetches()).toBe(1);
|
|
179
|
+
expect(navigate).toHaveBeenCalledOnce();
|
|
180
|
+
// The refresh did empty the cache and remount the page: the new instance
|
|
181
|
+
// joined the completion already running.
|
|
182
|
+
expect(mounts).toBe(2);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("redirects to the sign-in page once when the handoff fails", async () => {
|
|
186
|
+
fetchHandoff.mockRejectedValue(new Error("offline"));
|
|
187
|
+
mountCompletionPage();
|
|
188
|
+
await vi.waitFor(() => expect(navigate).toHaveBeenCalled());
|
|
189
|
+
await settle();
|
|
190
|
+
|
|
191
|
+
expect(fetchHandoff).toHaveBeenCalledOnce();
|
|
192
|
+
expect(navigate).toHaveBeenCalledExactlyOnceWith(
|
|
193
|
+
{ path: "/auth", query: { oauth_error: "error.oauth.failed" } },
|
|
194
|
+
{ replace: true },
|
|
195
|
+
);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("completes again on a later visit, once the previous one settled", async () => {
|
|
199
|
+
mountCompletionPage();
|
|
200
|
+
await vi.waitFor(() => expect(navigate).toHaveBeenCalledOnce());
|
|
201
|
+
await settle();
|
|
202
|
+
app.unmount();
|
|
203
|
+
|
|
204
|
+
mountCompletionPage();
|
|
205
|
+
await vi.waitFor(() => expect(navigate).toHaveBeenCalledTimes(2));
|
|
206
|
+
await settle();
|
|
207
|
+
|
|
208
|
+
expect(fetchHandoff).toHaveBeenCalledTimes(2);
|
|
209
|
+
});
|
|
@@ -12,7 +12,7 @@ beforeEach(() => {
|
|
|
12
12
|
vi.stubGlobal("defineDmsMiddleware", (handler: Middleware) => handler);
|
|
13
13
|
vi.stubGlobal("navigateDms", navigateDms);
|
|
14
14
|
vi.stubGlobal("useOnboarding", onboarding);
|
|
15
|
-
vi.stubGlobal("
|
|
15
|
+
vi.stubGlobal("useDmsUserSession", () => session);
|
|
16
16
|
vi.stubGlobal("useHomepage", () => "/dashboard");
|
|
17
17
|
});
|
|
18
18
|
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
/**
|
|
3
|
+
* The onboarding status the global middleware reads while a server renders a
|
|
4
|
+
* page reaches the browser with the page, through the runtime's real state
|
|
5
|
+
* transfer: the browser's run of the middleware asks nothing.
|
|
6
|
+
*/
|
|
7
|
+
import { createApp, createSSRApp, defineComponent, h, nextTick } from "vue";
|
|
8
|
+
import { renderToString } from "vue/server-renderer";
|
|
9
|
+
import { afterEach, beforeEach, expect, it, vi } from "vitest";
|
|
10
|
+
import type { App } from "vue";
|
|
11
|
+
import type { DmsFrontendRuntime } from "#dms/frontend-module";
|
|
12
|
+
|
|
13
|
+
const engine = await import("#dms/frontend-module");
|
|
14
|
+
vi.stubGlobal("useDmsState", engine.useDmsState);
|
|
15
|
+
vi.stubGlobal("defineDmsMiddleware", engine.defineDmsMiddleware);
|
|
16
|
+
vi.stubGlobal("navigateDms", engine.navigateDms);
|
|
17
|
+
vi.stubGlobal("useDmsUserSession", () => ({ loggedIn: { value: true } }));
|
|
18
|
+
vi.stubGlobal("useHomepage", () => "/dashboard");
|
|
19
|
+
|
|
20
|
+
const $authFetch = vi.fn(async () => ({ has_onboarded: true }));
|
|
21
|
+
vi.stubGlobal("useAuthFetch", () => ({ $authFetch }));
|
|
22
|
+
// The status is read once per app however many callers ask (the preload
|
|
23
|
+
// started with the app, the middleware).
|
|
24
|
+
// No DMS app runs here: each runtime holds the deduper on a state of its own.
|
|
25
|
+
vi.stubGlobal(
|
|
26
|
+
"useDmsApp",
|
|
27
|
+
() => engine.useDmsState("onboarding-test-app", () => ({})).value,
|
|
28
|
+
);
|
|
29
|
+
const { createTrailingDeduper } = await import(
|
|
30
|
+
"../layers/dms-core/app/utils/trailingDeduper"
|
|
31
|
+
);
|
|
32
|
+
vi.stubGlobal("createTrailingDeduper", createTrailingDeduper);
|
|
33
|
+
const { useTrailingDeduper } = await import(
|
|
34
|
+
"../layers/dms-core/app/composables/useTrailingDeduper"
|
|
35
|
+
);
|
|
36
|
+
vi.stubGlobal("useTrailingDeduper", useTrailingDeduper);
|
|
37
|
+
|
|
38
|
+
const { useOnboarding } = await import(
|
|
39
|
+
"../layers/dms-onboarding/app/composables/onboarding/useOnboarding"
|
|
40
|
+
);
|
|
41
|
+
vi.stubGlobal("useOnboarding", useOnboarding);
|
|
42
|
+
const { default: onboardingMiddleware } = await import(
|
|
43
|
+
"../layers/dms-onboarding/app/middleware/onboarding.global"
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const Page = defineComponent({
|
|
47
|
+
setup() {
|
|
48
|
+
engine.defineDmsPageMeta({});
|
|
49
|
+
const info = engine.useDmsState<{ hasOnboarded: boolean } | null>(
|
|
50
|
+
"dms-onboarding-info",
|
|
51
|
+
);
|
|
52
|
+
return () => h("p", info.value?.hasOnboarded ? "onboarded" : "unknown");
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const mounted: App[] = [];
|
|
57
|
+
|
|
58
|
+
function attach(app: App, runtime: DmsFrontendRuntime): void {
|
|
59
|
+
runtime.route.path = "/dashboard";
|
|
60
|
+
runtime.route.fullPath = "/dashboard";
|
|
61
|
+
engine.provideDmsFrontendRuntime(app, runtime);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
beforeEach(async () => {
|
|
65
|
+
$authFetch.mockClear();
|
|
66
|
+
await engine.setupFrontendModules([
|
|
67
|
+
{
|
|
68
|
+
options: { public: {} },
|
|
69
|
+
module: {
|
|
70
|
+
setup(sdk) {
|
|
71
|
+
sdk.registerMiddleware("onboarding", onboardingMiddleware, {
|
|
72
|
+
global: true,
|
|
73
|
+
});
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
]);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
afterEach(() => {
|
|
81
|
+
mounted.splice(0).forEach((app) => app.unmount());
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("hands the onboarding status the server read to the browser", async () => {
|
|
85
|
+
const server = engine.createDmsFrontendRuntime(undefined, {}, true);
|
|
86
|
+
const serverApp = createSSRApp(Page);
|
|
87
|
+
attach(serverApp, server);
|
|
88
|
+
expect(await renderToString(serverApp)).toBe("<p>onboarded</p>");
|
|
89
|
+
expect($authFetch).toHaveBeenCalledTimes(1);
|
|
90
|
+
const payload = structuredClone(engine.serializeDmsPayload(server));
|
|
91
|
+
expect(payload.state["dms-onboarding-info"]).toEqual({ hasOnboarded: true });
|
|
92
|
+
|
|
93
|
+
$authFetch.mockClear();
|
|
94
|
+
const browser = engine.createDmsFrontendRuntime(undefined, {});
|
|
95
|
+
engine.restoreDmsPayload(browser, payload);
|
|
96
|
+
const browserApp = createApp(Page);
|
|
97
|
+
attach(browserApp, browser);
|
|
98
|
+
const host = document.createElement("div");
|
|
99
|
+
browserApp.mount(host);
|
|
100
|
+
mounted.push(browserApp);
|
|
101
|
+
await nextTick();
|
|
102
|
+
await new Promise((done) => setTimeout(done, 0));
|
|
103
|
+
|
|
104
|
+
expect($authFetch).not.toHaveBeenCalled();
|
|
105
|
+
expect(host.textContent).toBe("onboarded");
|
|
106
|
+
});
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
/**
|
|
3
|
+
* A page's `useApiData` reads follow the page: `refreshPageBlocks()` (an
|
|
4
|
+
* action of the user) and an event on one of the page's realtime topics read
|
|
5
|
+
* them again through `refreshDmsPageData()`, without a `refresh()` of their
|
|
6
|
+
* own. A read that refreshes on its own triggers opts out.
|
|
7
|
+
*/
|
|
8
|
+
import { type App, createApp, defineComponent, h, nextTick, ref } from "vue";
|
|
9
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
10
|
+
import type { RealtimeTopicHandler } from "../layers/dms-core/app/composables/realtime/useUserRealtime";
|
|
11
|
+
import { useRealDataEngine } from "./fixtures/dataEngine";
|
|
12
|
+
import { refreshPageBlocks } from "../layers/dms-ui/app/utils/blockRefresh";
|
|
13
|
+
|
|
14
|
+
const requests: string[] = [];
|
|
15
|
+
// Holds the answers back while set.
|
|
16
|
+
let held: Promise<void> | undefined;
|
|
17
|
+
vi.stubGlobal("useAuthFetch", () => ({
|
|
18
|
+
$authFetch: async (url: string) => {
|
|
19
|
+
requests.push(url);
|
|
20
|
+
await held;
|
|
21
|
+
return { url };
|
|
22
|
+
},
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
const { useApiData } = await import(
|
|
26
|
+
"../layers/dms-core/app/composables/auth/useApiData"
|
|
27
|
+
);
|
|
28
|
+
const { usePageDataRefresh } = await import(
|
|
29
|
+
"../layers/dms-ui/app/build/composables/live/usePageDataRefresh"
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
// Past the one-frame window a burst of refreshes collapses into.
|
|
33
|
+
const settle = () => new Promise((resolve) => setTimeout(resolve, 40));
|
|
34
|
+
|
|
35
|
+
const pageTopics = ref<string[]>([]);
|
|
36
|
+
const handlers = new Map<string, Set<RealtimeTopicHandler>>();
|
|
37
|
+
function subscribe(topic: string, handler: RealtimeTopicHandler) {
|
|
38
|
+
if (!handlers.has(topic)) handlers.set(topic, new Set());
|
|
39
|
+
handlers.get(topic)!.add(handler);
|
|
40
|
+
return () => handlers.get(topic)?.delete(handler);
|
|
41
|
+
}
|
|
42
|
+
function publish(topic: string, event: object): void {
|
|
43
|
+
for (const handler of handlers.get(topic) ?? [])
|
|
44
|
+
handler(event as Parameters<RealtimeTopicHandler>[0]);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const mounted: App[] = [];
|
|
48
|
+
|
|
49
|
+
async function mountPage(): Promise<void> {
|
|
50
|
+
const Page = defineComponent({
|
|
51
|
+
setup() {
|
|
52
|
+
usePageDataRefresh({ pageTopics, subscribe });
|
|
53
|
+
useApiData("/api/orders");
|
|
54
|
+
useApiData("/api/own-triggers", { refreshOnPageRefresh: false });
|
|
55
|
+
return () => h("div");
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
const app = createApp(Page);
|
|
59
|
+
app.mount(document.createElement("div"));
|
|
60
|
+
mounted.push(app);
|
|
61
|
+
await settle();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
beforeEach(() => {
|
|
65
|
+
useRealDataEngine();
|
|
66
|
+
requests.length = 0;
|
|
67
|
+
pageTopics.value = [];
|
|
68
|
+
handlers.clear();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
afterEach(() => {
|
|
72
|
+
mounted.splice(0).forEach((app) => app.unmount());
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe("the page's data refresh", () => {
|
|
76
|
+
it("reads the page's data again on refreshPageBlocks()", async () => {
|
|
77
|
+
await mountPage();
|
|
78
|
+
expect(requests.sort()).toEqual(["/api/orders", "/api/own-triggers"]);
|
|
79
|
+
requests.length = 0;
|
|
80
|
+
|
|
81
|
+
refreshPageBlocks();
|
|
82
|
+
await settle();
|
|
83
|
+
|
|
84
|
+
expect(requests).toEqual(["/api/orders"]);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("resolves refreshPageBlocks() once the page's data is read again", async () => {
|
|
88
|
+
await mountPage();
|
|
89
|
+
requests.length = 0;
|
|
90
|
+
let release!: () => void;
|
|
91
|
+
held = new Promise<void>((resolve) => (release = resolve));
|
|
92
|
+
let done = false;
|
|
93
|
+
const refreshed = refreshPageBlocks().then(() => (done = true));
|
|
94
|
+
await settle();
|
|
95
|
+
expect(requests).toEqual(["/api/orders"]);
|
|
96
|
+
expect(done).toBe(false);
|
|
97
|
+
|
|
98
|
+
release();
|
|
99
|
+
held = undefined;
|
|
100
|
+
await refreshed;
|
|
101
|
+
expect(done).toBe(true);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("joins the refreshes of one burst", async () => {
|
|
105
|
+
await mountPage();
|
|
106
|
+
requests.length = 0;
|
|
107
|
+
await Promise.all([refreshPageBlocks(), refreshPageBlocks()]);
|
|
108
|
+
expect(requests).toEqual(["/api/orders"]);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("reads it again on an event of one of the page's topics", async () => {
|
|
112
|
+
await mountPage();
|
|
113
|
+
pageTopics.value = ["table:orders"];
|
|
114
|
+
await nextTick();
|
|
115
|
+
requests.length = 0;
|
|
116
|
+
|
|
117
|
+
publish("table:orders", { topic: "table:orders", type: "updated" });
|
|
118
|
+
publish("table:orders", { topic: "table:orders", type: "updated" });
|
|
119
|
+
await settle();
|
|
120
|
+
expect(requests).toEqual(["/api/orders"]);
|
|
121
|
+
|
|
122
|
+
// A presence snapshot rides the topic too, and changes no data.
|
|
123
|
+
requests.length = 0;
|
|
124
|
+
publish("table:orders", { topic: "table:orders", entries: [] });
|
|
125
|
+
await settle();
|
|
126
|
+
expect(requests).toEqual([]);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("stops following the topics the page no longer has", async () => {
|
|
130
|
+
await mountPage();
|
|
131
|
+
pageTopics.value = ["table:orders"];
|
|
132
|
+
await nextTick();
|
|
133
|
+
pageTopics.value = ["table:invoices"];
|
|
134
|
+
await nextTick();
|
|
135
|
+
requests.length = 0;
|
|
136
|
+
|
|
137
|
+
publish("table:orders", { topic: "table:orders", type: "updated" });
|
|
138
|
+
await settle();
|
|
139
|
+
expect(requests).toEqual([]);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
@@ -28,6 +28,16 @@ import {
|
|
|
28
28
|
usePageFillHeight,
|
|
29
29
|
} from "../layers/dms-layout/app/composables/layout/usePageFillHeight";
|
|
30
30
|
|
|
31
|
+
// The page's data refresh and the visit's navigation badges read the
|
|
32
|
+
// realtime session and Inertia's page, which these tests do not run.
|
|
33
|
+
vi.mock(
|
|
34
|
+
"../layers/dms-ui/app/build/composables/live/usePageDataRefresh",
|
|
35
|
+
() => ({ usePageDataRefresh: () => {} }),
|
|
36
|
+
);
|
|
37
|
+
vi.mock(
|
|
38
|
+
"../layers/dms-layout/app/build/composables/navigation/useVisitNavBadges",
|
|
39
|
+
() => ({ useVisitNavBadges: () => {} }),
|
|
40
|
+
);
|
|
31
41
|
vi.mock(
|
|
32
42
|
"../layers/dms-layout/app/build/components/layout/DashboardSidebar.vue",
|
|
33
43
|
() => ({ default: () => null }),
|
|
@@ -151,7 +161,7 @@ function installRuntime(): void {
|
|
|
151
161
|
PAGE_FILL_HEIGHT_CLASSES,
|
|
152
162
|
providePageFillHeight,
|
|
153
163
|
usePageFillHeight,
|
|
154
|
-
|
|
164
|
+
createDmsError: vi.fn(),
|
|
155
165
|
defineDmsPageMeta: vi.fn(),
|
|
156
166
|
firstAccessiblePagePath: vi.fn(),
|
|
157
167
|
navigateDms: vi.fn(),
|
|
@@ -159,7 +169,7 @@ function installRuntime(): void {
|
|
|
159
169
|
providePageRealtime: vi.fn(),
|
|
160
170
|
resolveDmsComponent: () => "div",
|
|
161
171
|
runCustomValidation: vi.fn(),
|
|
162
|
-
|
|
172
|
+
showDmsError: vi.fn(),
|
|
163
173
|
validateRequiredQueryParams: vi.fn(),
|
|
164
174
|
useDefinedFunctions: () => ({ getFunction: () => undefined }),
|
|
165
175
|
useDmsCookie: <T>(_name: string, options: { default?: () => T } = {}) =>
|
|
@@ -179,6 +189,9 @@ function installRuntime(): void {
|
|
|
179
189
|
reconcileSession: async () => {},
|
|
180
190
|
redirectToAuth: async () => {},
|
|
181
191
|
}),
|
|
192
|
+
// The page brought its site layout.
|
|
193
|
+
useRenderedSiteLayout: () => {},
|
|
194
|
+
useRenderedBellCounts: () => {},
|
|
182
195
|
useSiteLayout: () => ({
|
|
183
196
|
siteLayout: ref({ pages: {} }),
|
|
184
197
|
siteLayoutTree: ref([]),
|
|
@@ -197,7 +210,7 @@ function installRuntime(): void {
|
|
|
197
210
|
}),
|
|
198
211
|
useI18n: () => ({ t: (key: string) => key }),
|
|
199
212
|
useTranslation: () => ({ processI18n: (value: string) => value }),
|
|
200
|
-
|
|
213
|
+
useDmsUserSession: () => ({ loggedIn: ref(true) }),
|
|
201
214
|
};
|
|
202
215
|
Object.entries(runtime).forEach(([name, value]) =>
|
|
203
216
|
vi.stubGlobal(name, value),
|