@antelopejs/dms 0.8.3 → 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 +20 -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/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/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,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The renderer's real data layer for a test: its async data, state and cookie
|
|
3
|
+
* composables, and dms-core's `useApiData` over the test's `useAuthFetch`
|
|
4
|
+
* stub, as the auto-imports a layer reaches. Each call starts a fresh runtime
|
|
5
|
+
* — a browser one under jsdom, a server one otherwise — so no data a test
|
|
6
|
+
* loaded leaks into the next.
|
|
7
|
+
*/
|
|
8
|
+
import { createApp } from "vue";
|
|
9
|
+
import { vi } from "vitest";
|
|
10
|
+
|
|
11
|
+
const engine = await import("#dms/frontend-module");
|
|
12
|
+
const { useApiData, useApiPagedData } = await import(
|
|
13
|
+
"../../layers/dms-core/app/composables/auth/useApiData"
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
const ENGINE_GLOBALS = {
|
|
17
|
+
useDmsAsyncData: engine.useDmsAsyncData,
|
|
18
|
+
useDmsFetch: engine.useDmsFetch,
|
|
19
|
+
useDmsPagedData: engine.useDmsPagedData,
|
|
20
|
+
useDmsState: engine.useDmsState,
|
|
21
|
+
useDmsCookie: engine.useDmsCookie,
|
|
22
|
+
useApiData,
|
|
23
|
+
useApiPagedData,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export function useRealDataEngine(): ReturnType<
|
|
27
|
+
typeof engine.createDmsFrontendRuntime
|
|
28
|
+
> {
|
|
29
|
+
for (const [name, composable] of Object.entries(ENGINE_GLOBALS))
|
|
30
|
+
vi.stubGlobal(name, composable);
|
|
31
|
+
const isServer = typeof window === "undefined";
|
|
32
|
+
const runtime = engine.createDmsFrontendRuntime(
|
|
33
|
+
undefined,
|
|
34
|
+
undefined,
|
|
35
|
+
isServer,
|
|
36
|
+
);
|
|
37
|
+
if (isServer) engine.setDmsServerRuntimeResolver(() => runtime);
|
|
38
|
+
else
|
|
39
|
+
engine.provideDmsFrontendRuntime(
|
|
40
|
+
createApp({ render: () => null }),
|
|
41
|
+
runtime,
|
|
42
|
+
);
|
|
43
|
+
return runtime;
|
|
44
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
|
-
import { createSSRApp, defineComponent, h, ref } from "vue";
|
|
2
|
+
import { createSSRApp, defineComponent, h, nextTick, ref } from "vue";
|
|
3
3
|
import { renderToString } from "vue/server-renderer";
|
|
4
4
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
5
|
import FormLoadError from "../layers/dms-ui/app/build/components/form/FormLoadError.vue";
|
|
@@ -66,6 +66,41 @@ describe("a form's record load", () => {
|
|
|
66
66
|
expect(apply).toHaveBeenCalledWith(ROW);
|
|
67
67
|
});
|
|
68
68
|
|
|
69
|
+
it("takes a later read of the record, its values becoming the saved ones", async () => {
|
|
70
|
+
const apply = vi.fn();
|
|
71
|
+
const onReloaded = vi.fn();
|
|
72
|
+
const source = loadOf(ROW, null);
|
|
73
|
+
trackFormRecordLoad(source, apply, { onReloaded });
|
|
74
|
+
const fresh = { values: { email: "fresh@example.com" }, initial: {} };
|
|
75
|
+
source.data.value = fresh;
|
|
76
|
+
await nextTick();
|
|
77
|
+
expect(apply).toHaveBeenLastCalledWith(fresh);
|
|
78
|
+
expect(onReloaded).toHaveBeenCalledOnce();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("leaves a later read out while the user edits", async () => {
|
|
82
|
+
const apply = vi.fn();
|
|
83
|
+
const onReloaded = vi.fn();
|
|
84
|
+
const source = loadOf(ROW, null);
|
|
85
|
+
trackFormRecordLoad(source, apply, {
|
|
86
|
+
isEditing: () => true,
|
|
87
|
+
onReloaded,
|
|
88
|
+
});
|
|
89
|
+
source.data.value = { values: { email: "other@example.com" }, initial: {} };
|
|
90
|
+
await nextTick();
|
|
91
|
+
expect(apply).toHaveBeenCalledOnce();
|
|
92
|
+
expect(onReloaded).not.toHaveBeenCalled();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("stops showing the cached record once a later read is refused", async () => {
|
|
96
|
+
const apply = vi.fn();
|
|
97
|
+
const source = loadOf(ROW, null);
|
|
98
|
+
const load = trackFormRecordLoad(source, apply);
|
|
99
|
+
source.error.value = FORBIDDEN;
|
|
100
|
+
await nextTick();
|
|
101
|
+
expect(load.failure.value).toBe("forbidden");
|
|
102
|
+
});
|
|
103
|
+
|
|
69
104
|
it("applies a record that loaded", () => {
|
|
70
105
|
const apply = vi.fn();
|
|
71
106
|
const load = trackFormRecordLoad(loadOf(ROW, null), apply);
|
|
@@ -74,6 +109,25 @@ describe("a form's record load", () => {
|
|
|
74
109
|
});
|
|
75
110
|
});
|
|
76
111
|
|
|
112
|
+
describe("Form.vue", () => {
|
|
113
|
+
const source = readFileSync(
|
|
114
|
+
new URL("../layers/dms-ui/app/components/form/Form.vue", import.meta.url),
|
|
115
|
+
"utf8",
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
it("leaves reading the record again on mount to the data layer", () => {
|
|
119
|
+
// The renderer reads cached data again on each mount (stale while
|
|
120
|
+
// revalidate): no status reset forces it any more.
|
|
121
|
+
expect(source).not.toMatch(/status\.value\s*=\s*"idle"/);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("keys the record's load on the record it reads", () => {
|
|
125
|
+
expect(source).toMatch(
|
|
126
|
+
/`form-\$\{props\.componentId\}-\$\{props\.pageId\}-\$\{resolvedFetchUrl\.value/,
|
|
127
|
+
);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
77
131
|
describe("a form about a record its address names none of", () => {
|
|
78
132
|
const FETCH_URL = "/api/tasks/get?id={{query.id}}";
|
|
79
133
|
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `useApiData` on the runtime's real async data, over a fake `$authFetch`:
|
|
3
|
+
* what a layer component reads, a server render of it, and the browser
|
|
4
|
+
* hydrating its HTML.
|
|
5
|
+
*/
|
|
6
|
+
import { createApp, createSSRApp, type App, type Component } from "vue";
|
|
7
|
+
import { renderToString } from "vue/server-renderer";
|
|
8
|
+
import { vi } from "vitest";
|
|
9
|
+
import type { DmsFrontendRuntime, DmsPayload } from "#dms/frontend-module";
|
|
10
|
+
|
|
11
|
+
export const engine = await import("#dms/frontend-module");
|
|
12
|
+
const { useApiData } = await import(
|
|
13
|
+
"../../layers/dms-core/app/composables/auth/useApiData"
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
/** Stubs the globals `useApiData` reads, `$authFetch` being `authFetch`. */
|
|
17
|
+
export function stubApiData(authFetch: (...args: never[]) => unknown): void {
|
|
18
|
+
vi.stubGlobal("useDmsFetch", engine.useDmsFetch);
|
|
19
|
+
vi.stubGlobal("useDmsAsyncData", engine.useDmsAsyncData);
|
|
20
|
+
vi.stubGlobal("useDmsState", engine.useDmsState);
|
|
21
|
+
vi.stubGlobal("useAuthFetch", () => ({ $authFetch: authFetch }));
|
|
22
|
+
vi.stubGlobal("useApiData", useApiData);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** A browser runtime: `useApiData` outside a component reads through it. */
|
|
26
|
+
export function browserRuntime(app: App = createApp({})): DmsFrontendRuntime {
|
|
27
|
+
const runtime = engine.createDmsFrontendRuntime();
|
|
28
|
+
engine.provideDmsFrontendRuntime(app, runtime);
|
|
29
|
+
return runtime;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Renders `root` as a server would: its HTML and the payload it hands over. */
|
|
33
|
+
export async function serverRender(
|
|
34
|
+
root: Component,
|
|
35
|
+
setup?: (app: App) => void,
|
|
36
|
+
): Promise<{ html: string; payload: DmsPayload }> {
|
|
37
|
+
const runtime = engine.createDmsFrontendRuntime(undefined, {}, true);
|
|
38
|
+
const app = createSSRApp(root);
|
|
39
|
+
engine.provideDmsFrontendRuntime(app, runtime);
|
|
40
|
+
setup?.(app);
|
|
41
|
+
const html = await renderToString(app);
|
|
42
|
+
return {
|
|
43
|
+
html,
|
|
44
|
+
payload: structuredClone(engine.serializeDmsPayload(runtime)),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Hydrates the server's `html` with its `payload`, as the browser does. */
|
|
49
|
+
export function hydrate(
|
|
50
|
+
root: Component,
|
|
51
|
+
html: string,
|
|
52
|
+
payload: DmsPayload,
|
|
53
|
+
setup?: (app: App) => void,
|
|
54
|
+
): { app: App; container: HTMLElement } {
|
|
55
|
+
const runtime = engine.createDmsFrontendRuntime();
|
|
56
|
+
engine.restoreDmsPayload(runtime, payload);
|
|
57
|
+
const container = document.createElement("div");
|
|
58
|
+
container.innerHTML = html;
|
|
59
|
+
const app = createSSRApp(root);
|
|
60
|
+
engine.provideDmsFrontendRuntime(app, runtime);
|
|
61
|
+
setup?.(app);
|
|
62
|
+
app.mount(container);
|
|
63
|
+
return { app, container };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Mounts `root` in the browser, past the hydration (a client navigation). */
|
|
67
|
+
export function mountInBrowser(
|
|
68
|
+
root: Component,
|
|
69
|
+
setup?: (app: App) => void,
|
|
70
|
+
): { app: App; container: HTMLElement } {
|
|
71
|
+
const container = document.createElement("div");
|
|
72
|
+
const app = createApp(root);
|
|
73
|
+
browserRuntime(app);
|
|
74
|
+
setup?.(app);
|
|
75
|
+
app.mount(container);
|
|
76
|
+
return { app, container };
|
|
77
|
+
}
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
// @vitest-environment jsdom
|
|
2
2
|
import {
|
|
3
3
|
computed,
|
|
4
|
-
createSSRApp,
|
|
5
4
|
defineComponent,
|
|
6
5
|
h,
|
|
7
6
|
inject,
|
|
8
7
|
onBeforeUnmount,
|
|
8
|
+
onScopeDispose,
|
|
9
9
|
ref,
|
|
10
|
+
shallowReactive,
|
|
10
11
|
useId,
|
|
11
12
|
watch,
|
|
12
13
|
type App,
|
|
13
14
|
} from "vue";
|
|
14
|
-
import { renderToString } from "vue/server-renderer";
|
|
15
15
|
import { afterEach, beforeEach, expect, it, vi } from "vitest";
|
|
16
16
|
import Image from "../layers/dms-ui/app/components/form/components/Image.vue";
|
|
17
|
+
import { hydrate, serverRender, stubApiData } from "./helpers/api-data-runtime";
|
|
17
18
|
|
|
18
19
|
const STORED_IMAGE = { key: "files/profile/cover.png", alt: "Cover" };
|
|
19
20
|
|
|
@@ -25,16 +26,14 @@ const passthrough = defineComponent({
|
|
|
25
26
|
slots.default?.(),
|
|
26
27
|
});
|
|
27
28
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
});
|
|
29
|
+
const Root = { render: () => h(Image, { modelValue: STORED_IMAGE }) };
|
|
30
|
+
|
|
31
|
+
function register(app: App): void {
|
|
32
32
|
app.component("UButton", defineComponent({ setup: () => () => h("button") }));
|
|
33
33
|
app.component("UIcon", defineComponent({ setup: () => () => h("span") }));
|
|
34
34
|
app.component("UInput", defineComponent({ setup: () => () => h("input") }));
|
|
35
35
|
app.component("UTooltip", passthrough);
|
|
36
36
|
app.component("UModal", defineComponent({ setup: () => () => null }));
|
|
37
|
-
return app;
|
|
38
37
|
}
|
|
39
38
|
|
|
40
39
|
beforeEach(() => {
|
|
@@ -44,12 +43,17 @@ beforeEach(() => {
|
|
|
44
43
|
vi.stubGlobal("inject", inject);
|
|
45
44
|
vi.stubGlobal("useId", useId);
|
|
46
45
|
vi.stubGlobal("onBeforeUnmount", onBeforeUnmount);
|
|
46
|
+
vi.stubGlobal("onScopeDispose", onScopeDispose);
|
|
47
|
+
vi.stubGlobal("shallowReactive", shallowReactive);
|
|
47
48
|
vi.stubGlobal("FORM_FIELD_LOADING_KEY", Symbol());
|
|
48
49
|
vi.stubGlobal("FORM_CONTENT_LANGUAGE_KEY", Symbol());
|
|
49
50
|
vi.stubGlobal("CONTENT_LANGUAGE_HEADER", "x-content-language");
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
stubApiData((async () => ({
|
|
52
|
+
resourceKey: STORED_IMAGE.key,
|
|
53
|
+
filename: "cover.png",
|
|
54
|
+
size: 10,
|
|
55
|
+
url: "https://files.test/cover.png",
|
|
56
|
+
})) as never);
|
|
53
57
|
vi.stubGlobal("useUploadWithProgress", () => ({
|
|
54
58
|
uploadWithProgress: vi.fn(),
|
|
55
59
|
}));
|
|
@@ -72,15 +76,16 @@ afterEach(() => {
|
|
|
72
76
|
});
|
|
73
77
|
|
|
74
78
|
it("hydrates a stored single image without an attribute mismatch", async () => {
|
|
75
|
-
|
|
76
|
-
const
|
|
77
|
-
|
|
79
|
+
vi.stubEnv("SSR", true);
|
|
80
|
+
const { html, payload } = await serverRender(Root, register);
|
|
81
|
+
vi.unstubAllEnvs();
|
|
78
82
|
const warnings: string[] = [];
|
|
79
|
-
const app = createImageApp();
|
|
80
|
-
app.config.warnHandler = (message) => warnings.push(message);
|
|
81
83
|
const consoleError = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
82
84
|
|
|
83
|
-
app
|
|
85
|
+
const { app, container } = hydrate(Root, html, payload, (application) => {
|
|
86
|
+
register(application);
|
|
87
|
+
application.config.warnHandler = (message) => warnings.push(message);
|
|
88
|
+
});
|
|
84
89
|
|
|
85
90
|
expect(warnings.filter((message) => message.includes("Hydration"))).toEqual(
|
|
86
91
|
[],
|
|
@@ -2,30 +2,29 @@ import { readFileSync } from "node:fs";
|
|
|
2
2
|
import { transpileModule } from "typescript";
|
|
3
3
|
import {
|
|
4
4
|
computed,
|
|
5
|
-
|
|
5
|
+
createApp,
|
|
6
6
|
defineComponent,
|
|
7
7
|
effectScope,
|
|
8
8
|
getCurrentInstance,
|
|
9
9
|
h,
|
|
10
10
|
nextTick,
|
|
11
11
|
onScopeDispose,
|
|
12
|
-
onServerPrefetch,
|
|
13
12
|
reactive,
|
|
14
13
|
ref,
|
|
14
|
+
shallowReactive,
|
|
15
15
|
watch,
|
|
16
16
|
useId,
|
|
17
|
+
type App,
|
|
17
18
|
type EffectScope,
|
|
18
19
|
type Ref,
|
|
19
20
|
} from "vue";
|
|
20
|
-
import { renderToString } from "vue/server-renderer";
|
|
21
21
|
import { afterEach, beforeEach, expect, it, vi } from "vitest";
|
|
22
22
|
import Image from "../layers/dms-ui/app/components/form/components/Image.vue";
|
|
23
23
|
import { useKeyboardPlatform } from "../layers/dms-ui/app/composables/global/keyboardPlatform";
|
|
24
24
|
import { fieldErrorId } from "../layers/dms-core/app/composables/useFieldErrors";
|
|
25
|
-
import {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
} from "../layers/dms-ui/app/utils/metadataRetry";
|
|
25
|
+
import { useFileMetadata } from "../layers/dms-ui/app/build/composables/files/useFileMetadata";
|
|
26
|
+
import { FORM_CONTENT_LANGUAGE_KEY } from "../layers/dms-ui/app/composables/form/types/content-language";
|
|
27
|
+
import { engine, serverRender, stubApiData } from "./helpers/api-data-runtime";
|
|
29
28
|
|
|
30
29
|
interface ImageValue {
|
|
31
30
|
key: string;
|
|
@@ -47,13 +46,14 @@ interface ImageHarness {
|
|
|
47
46
|
emitValue: () => void;
|
|
48
47
|
removeItem: (id: string) => void;
|
|
49
48
|
addFiles: (files: File[]) => void;
|
|
50
|
-
fetchItemMetadata: (item: GalleryEntry, force?: boolean) => Promise<void>;
|
|
51
49
|
}
|
|
52
50
|
|
|
53
51
|
const emit = vi.fn();
|
|
54
52
|
const authFetch = vi.fn();
|
|
55
53
|
const upload = vi.fn();
|
|
56
54
|
let scope: EffectScope;
|
|
55
|
+
// The application the script runs in: its DMS runtime reads the metadata.
|
|
56
|
+
let app: App;
|
|
57
57
|
const props = reactive({ modelValue: [] as ImageValue[], multiple: true });
|
|
58
58
|
const original: ImageValue = {
|
|
59
59
|
key: "legacy/private.png",
|
|
@@ -78,15 +78,26 @@ function loadImage(): ImageHarness {
|
|
|
78
78
|
.replaceAll("import.meta.env.SSR", "false")
|
|
79
79
|
.replace(/^export type /gm, "type ");
|
|
80
80
|
const { outputText } = transpileModule(source, {});
|
|
81
|
-
return
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
81
|
+
return app.runWithContext(() =>
|
|
82
|
+
scope.run(() =>
|
|
83
|
+
new Function(
|
|
84
|
+
`${outputText}; return { items, emitValue, removeItem, addFiles };`,
|
|
85
|
+
)(),
|
|
86
|
+
),
|
|
85
87
|
)!;
|
|
86
88
|
}
|
|
87
89
|
|
|
90
|
+
/** Lets the metadata reads answer. */
|
|
91
|
+
const settle = async () => {
|
|
92
|
+
for (let tick = 0; tick < 5; tick++) await nextTick();
|
|
93
|
+
await new Promise((done) => setTimeout(done, 0));
|
|
94
|
+
};
|
|
95
|
+
|
|
88
96
|
beforeEach(() => {
|
|
89
97
|
scope = effectScope();
|
|
98
|
+
app = createApp({});
|
|
99
|
+
engine.provideDmsFrontendRuntime(app, engine.createDmsFrontendRuntime());
|
|
100
|
+
vi.stubEnv("SSR", false);
|
|
90
101
|
props.modelValue = [original, sibling];
|
|
91
102
|
authFetch.mockImplementation((_url, options) =>
|
|
92
103
|
options.query?.resourceKey === original.key
|
|
@@ -98,8 +109,8 @@ beforeEach(() => {
|
|
|
98
109
|
}),
|
|
99
110
|
);
|
|
100
111
|
vi.stubGlobal("defineProps", () => props);
|
|
101
|
-
vi.stubGlobal("
|
|
102
|
-
vi.stubGlobal("
|
|
112
|
+
vi.stubGlobal("useFileMetadata", useFileMetadata);
|
|
113
|
+
vi.stubGlobal("shallowReactive", shallowReactive);
|
|
103
114
|
vi.stubGlobal("defineEmits", () => emit);
|
|
104
115
|
vi.stubGlobal("ref", ref);
|
|
105
116
|
vi.stubGlobal("computed", computed);
|
|
@@ -107,10 +118,9 @@ beforeEach(() => {
|
|
|
107
118
|
vi.stubGlobal("inject", () => null);
|
|
108
119
|
vi.stubGlobal("useId", () => (getCurrentInstance() ? useId() : "image"));
|
|
109
120
|
vi.stubGlobal("FORM_FIELD_LOADING_KEY", Symbol());
|
|
110
|
-
vi.stubGlobal("FORM_CONTENT_LANGUAGE_KEY", Symbol());
|
|
111
121
|
vi.stubGlobal("CONTENT_LANGUAGE_HEADER", "x-content-language");
|
|
112
122
|
vi.stubGlobal("onBeforeUnmount", onScopeDispose);
|
|
113
|
-
|
|
123
|
+
stubApiData(authFetch);
|
|
114
124
|
vi.stubGlobal("useUploadWithProgress", () => ({
|
|
115
125
|
uploadWithProgress: upload,
|
|
116
126
|
}));
|
|
@@ -135,13 +145,14 @@ beforeEach(() => {
|
|
|
135
145
|
|
|
136
146
|
afterEach(() => {
|
|
137
147
|
scope.stop();
|
|
148
|
+
vi.unstubAllEnvs();
|
|
138
149
|
vi.unstubAllGlobals();
|
|
139
150
|
vi.resetAllMocks();
|
|
140
151
|
});
|
|
141
152
|
|
|
142
153
|
it("preserves a denied image when another image is removed", async () => {
|
|
143
154
|
const image = loadImage();
|
|
144
|
-
await
|
|
155
|
+
await settle();
|
|
145
156
|
expect(image.items.value[0]!.status).toBe("unavailable");
|
|
146
157
|
image.removeItem(image.items.value[1]!.id);
|
|
147
158
|
expect(emit).toHaveBeenLastCalledWith("update:modelValue", [original]);
|
|
@@ -149,7 +160,7 @@ it("preserves a denied image when another image is removed", async () => {
|
|
|
149
160
|
|
|
150
161
|
it("preserves a denied image on value echo without reinitializing its identity", async () => {
|
|
151
162
|
const image = loadImage();
|
|
152
|
-
await
|
|
163
|
+
await settle();
|
|
153
164
|
const id = image.items.value[0]!.id;
|
|
154
165
|
props.modelValue = [{ ...original }, { ...sibling }];
|
|
155
166
|
await nextTick();
|
|
@@ -163,7 +174,7 @@ it("preserves a denied image on value echo without reinitializing its identity",
|
|
|
163
174
|
|
|
164
175
|
it("removes a denied reference only when explicitly removed", async () => {
|
|
165
176
|
const image = loadImage();
|
|
166
|
-
await
|
|
177
|
+
await settle();
|
|
167
178
|
image.removeItem(image.items.value[0]!.id);
|
|
168
179
|
expect(emit).toHaveBeenLastCalledWith("update:modelValue", [
|
|
169
180
|
{ ...sibling, principal: true },
|
|
@@ -172,7 +183,7 @@ it("removes a denied reference only when explicitly removed", async () => {
|
|
|
172
183
|
|
|
173
184
|
it("excludes a failed new upload while retaining existing references", async () => {
|
|
174
185
|
const image = loadImage();
|
|
175
|
-
await
|
|
186
|
+
await settle();
|
|
176
187
|
authFetch.mockRejectedValueOnce(new Error("Upload failed"));
|
|
177
188
|
image.addFiles([new File(["bytes"], "new.png", { type: "image/png" })]);
|
|
178
189
|
await vi.waitFor(() =>
|
|
@@ -187,35 +198,34 @@ it("excludes a failed new upload while retaining existing references", async ()
|
|
|
187
198
|
});
|
|
188
199
|
|
|
189
200
|
it("keeps the denied image and its explicit deletion control in rendered markup", async () => {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
const denied = Promise.reject(new Error("403"));
|
|
193
|
-
onServerPrefetch(() => denied.catch(() => undefined));
|
|
194
|
-
return denied;
|
|
195
|
-
},
|
|
196
|
-
}));
|
|
197
|
-
const app = createSSRApp({
|
|
198
|
-
render: () => h(Image, { modelValue: [original], multiple: true }),
|
|
199
|
-
});
|
|
200
|
-
app.component(
|
|
201
|
-
"UButton",
|
|
202
|
-
defineComponent({
|
|
203
|
-
props: ["label"],
|
|
204
|
-
setup: (buttonProps) => () => h("button", buttonProps.label),
|
|
205
|
-
}),
|
|
201
|
+
authFetch.mockRejectedValue(
|
|
202
|
+
Object.assign(new Error("Forbidden"), { statusCode: 403 }),
|
|
206
203
|
);
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
(
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
})
|
|
204
|
+
const register = (app: App) => {
|
|
205
|
+
app.component(
|
|
206
|
+
"UButton",
|
|
207
|
+
defineComponent({
|
|
208
|
+
props: ["label"],
|
|
209
|
+
setup: (buttonProps) => () => h("button", buttonProps.label),
|
|
210
|
+
}),
|
|
211
|
+
);
|
|
212
|
+
app.component("UIcon", defineComponent({ setup: () => () => h("span") }));
|
|
213
|
+
app.component(
|
|
214
|
+
"UTooltip",
|
|
215
|
+
defineComponent({
|
|
216
|
+
setup:
|
|
217
|
+
(_, { slots }) =>
|
|
218
|
+
() =>
|
|
219
|
+
slots.default?.(),
|
|
220
|
+
}),
|
|
221
|
+
);
|
|
222
|
+
app.component("UModal", defineComponent({ setup: () => () => null }));
|
|
223
|
+
};
|
|
224
|
+
vi.stubEnv("SSR", true);
|
|
225
|
+
const { html } = await serverRender(
|
|
226
|
+
{ render: () => h(Image, { modelValue: [original], multiple: true }) },
|
|
227
|
+
register,
|
|
216
228
|
);
|
|
217
|
-
app.component("UModal", defineComponent({ setup: () => () => null }));
|
|
218
|
-
const html = await renderToString(app);
|
|
219
229
|
expect(html).toContain("dms.form.image.file_unavailable");
|
|
220
230
|
expect(html).not.toContain("dms.form.image.upload_failed");
|
|
221
231
|
expect(html).toContain("dms.form.image.delete</button>");
|
|
@@ -225,11 +235,7 @@ it("keeps the denied image and its explicit deletion control in rendered markup"
|
|
|
225
235
|
it("refreshes an expiring image preview and retries a transient failure", async () => {
|
|
226
236
|
vi.useFakeTimers();
|
|
227
237
|
vi.setSystemTime(new Date("2026-09-14T12:00:00Z"));
|
|
228
|
-
|
|
229
|
-
vi.stubGlobal("FORM_CONTENT_LANGUAGE_KEY", contentLanguageKey);
|
|
230
|
-
vi.stubGlobal("inject", (key: symbol) =>
|
|
231
|
-
key === contentLanguageKey ? "*" : null,
|
|
232
|
-
);
|
|
238
|
+
app.provide(FORM_CONTENT_LANGUAGE_KEY, "*");
|
|
233
239
|
props.modelValue = [original];
|
|
234
240
|
authFetch
|
|
235
241
|
.mockReset()
|
|
@@ -248,7 +254,7 @@ it("refreshes an expiring image preview and retries a transient failure", async
|
|
|
248
254
|
});
|
|
249
255
|
|
|
250
256
|
const image = loadImage();
|
|
251
|
-
await
|
|
257
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
252
258
|
await vi.advanceTimersByTimeAsync(55000);
|
|
253
259
|
expect(image.items.value[0]!.url).toBe("https://example.test/first.png");
|
|
254
260
|
await vi.advanceTimersByTimeAsync(1000);
|
|
@@ -78,9 +78,23 @@ async function mount(props: Record<string, unknown>) {
|
|
|
78
78
|
app.component("UInputNumber", NumberField);
|
|
79
79
|
app.mount(host);
|
|
80
80
|
await nextTick();
|
|
81
|
+
await untilListenersAttached();
|
|
81
82
|
return { value, adjustments, input: host.querySelector("input")! };
|
|
82
83
|
}
|
|
83
84
|
|
|
85
|
+
// Vue ignores an event that reaches one of its listeners in the millisecond
|
|
86
|
+
// that listener was attached (runtime-dom's `_vts` check, against events
|
|
87
|
+
// fired while the patch that attached it runs). Enter bubbles from the field's
|
|
88
|
+
// own listener to the wrapper's, so an Enter fired in the millisecond of the
|
|
89
|
+
// mount never settled the field: about one run in two. A person never types
|
|
90
|
+
// that fast, so the field is used once the clock has moved on.
|
|
91
|
+
async function untilListenersAttached() {
|
|
92
|
+
const attached = Date.now();
|
|
93
|
+
while (Date.now() <= attached) {
|
|
94
|
+
await new Promise((resolve) => setTimeout(resolve, 1));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
84
98
|
async function type(input: HTMLInputElement, text: string) {
|
|
85
99
|
input.value = text;
|
|
86
100
|
input.dispatchEvent(new Event("input", { bubbles: true }));
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { createApp, defineComponent, h, reactive, ref } from "vue";
|
|
8
8
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
9
|
+
import { useRealDataEngine } from "./fixtures/dataEngine";
|
|
9
10
|
import { createI18n } from "vue-i18n";
|
|
10
11
|
|
|
11
12
|
const i18n = createI18n({
|
|
@@ -66,6 +67,8 @@ function copyButtons(root: HTMLElement): HTMLButtonElement[] {
|
|
|
66
67
|
return [...root.querySelectorAll<HTMLButtonElement>("button[title]")];
|
|
67
68
|
}
|
|
68
69
|
|
|
70
|
+
beforeEach(() => useRealDataEngine());
|
|
71
|
+
|
|
69
72
|
beforeEach(() => {
|
|
70
73
|
fetchedItems = [];
|
|
71
74
|
copied.splice(0);
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
formStateInjectionKey,
|
|
17
17
|
} from "@nuxt/ui/composables/useFormField";
|
|
18
18
|
import { sameFormValue } from "../layers/dms-ui/app/build/composables/unsaved-changes/formValue";
|
|
19
|
+
import { browserRuntime, stubApiData } from "./helpers/api-data-runtime";
|
|
19
20
|
|
|
20
21
|
const ROLES_URL = "/settings/workspace/members/role-options";
|
|
21
22
|
const ROLE_OPTIONS = {
|
|
@@ -74,6 +75,7 @@ async function mountPicker(props: Record<string, unknown> = {}) {
|
|
|
74
75
|
},
|
|
75
76
|
});
|
|
76
77
|
app = createApp(Field);
|
|
78
|
+
browserRuntime(app);
|
|
77
79
|
const stub = (tag: string) =>
|
|
78
80
|
defineComponent({
|
|
79
81
|
setup:
|
|
@@ -110,7 +112,7 @@ beforeEach(() => {
|
|
|
110
112
|
vi.stubGlobal("useI18n", () => ({ t: translate }));
|
|
111
113
|
authFetch.mockReset();
|
|
112
114
|
authFetch.mockResolvedValue(ROLE_OPTIONS);
|
|
113
|
-
|
|
115
|
+
stubApiData(authFetch);
|
|
114
116
|
fieldError.value = undefined;
|
|
115
117
|
formState.value = {};
|
|
116
118
|
model.value = [];
|
|
@@ -127,7 +129,7 @@ afterEach(() => {
|
|
|
127
129
|
it("offers the server's roles and picks and unpicks them", async () => {
|
|
128
130
|
await mountPicker();
|
|
129
131
|
|
|
130
|
-
expect(authFetch).toHaveBeenCalledWith(ROLES_URL);
|
|
132
|
+
expect(authFetch).toHaveBeenCalledWith(ROLES_URL, expect.anything());
|
|
131
133
|
expect(
|
|
132
134
|
[...host.querySelectorAll("button[data-value]")].map((b) =>
|
|
133
135
|
b.textContent?.trim(),
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { nextTick, ref } from "vue";
|
|
2
|
+
import { describe, expect, it, vi } from "vitest";
|
|
3
|
+
|
|
4
|
+
vi.stubGlobal("defineDmsPlugin", (setup: unknown) => setup);
|
|
5
|
+
const { watchRealtimeReconnects } = await import(
|
|
6
|
+
"../layers/dms-layout/app/plugins/dms-menu-sync.client"
|
|
7
|
+
);
|
|
8
|
+
|
|
9
|
+
// A realtime stream that drops and opens again missed the menu events sent
|
|
10
|
+
// meanwhile: the menu resyncs once it is back, but not on the first opening,
|
|
11
|
+
// which follows the page load that brought the current menu.
|
|
12
|
+
|
|
13
|
+
describe("the menu resync on a realtime reconnect", () => {
|
|
14
|
+
it("leaves the first opening alone and resyncs on each reopening", async () => {
|
|
15
|
+
const sessionId = ref<string | undefined>(undefined);
|
|
16
|
+
const onReconnect = vi.fn();
|
|
17
|
+
watchRealtimeReconnects(sessionId, onReconnect);
|
|
18
|
+
|
|
19
|
+
sessionId.value = "session-1";
|
|
20
|
+
await nextTick();
|
|
21
|
+
expect(onReconnect).not.toHaveBeenCalled();
|
|
22
|
+
|
|
23
|
+
sessionId.value = undefined;
|
|
24
|
+
await nextTick();
|
|
25
|
+
sessionId.value = "session-2";
|
|
26
|
+
await nextTick();
|
|
27
|
+
expect(onReconnect).toHaveBeenCalledOnce();
|
|
28
|
+
|
|
29
|
+
sessionId.value = undefined;
|
|
30
|
+
await nextTick();
|
|
31
|
+
sessionId.value = "session-3";
|
|
32
|
+
await nextTick();
|
|
33
|
+
expect(onReconnect).toHaveBeenCalledTimes(2);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("counts a stream already open when it starts as its first opening", async () => {
|
|
37
|
+
const sessionId = ref<string | undefined>("session-1");
|
|
38
|
+
const onReconnect = vi.fn();
|
|
39
|
+
watchRealtimeReconnects(sessionId, onReconnect);
|
|
40
|
+
|
|
41
|
+
sessionId.value = undefined;
|
|
42
|
+
await nextTick();
|
|
43
|
+
sessionId.value = "session-2";
|
|
44
|
+
await nextTick();
|
|
45
|
+
expect(onReconnect).toHaveBeenCalledOnce();
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -10,12 +10,14 @@ import {
|
|
|
10
10
|
onBeforeUnmount,
|
|
11
11
|
onScopeDispose,
|
|
12
12
|
ref,
|
|
13
|
+
shallowReactive,
|
|
13
14
|
useId,
|
|
14
15
|
watch,
|
|
15
16
|
} from "vue";
|
|
16
17
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
17
18
|
import File from "../layers/dms-ui/app/components/form/components/File.vue";
|
|
18
19
|
import Image from "../layers/dms-ui/app/components/form/components/Image.vue";
|
|
20
|
+
import { browserRuntime, stubApiData } from "./helpers/api-data-runtime";
|
|
19
21
|
import {
|
|
20
22
|
isDefinitiveMetadataFailure,
|
|
21
23
|
resolveMetadataRetryDelay,
|
|
@@ -80,6 +82,7 @@ function mountField(field: FieldCase): { app: App; container: HTMLElement } {
|
|
|
80
82
|
const app = createApp({
|
|
81
83
|
render: () => h(field.component, { modelValue: field.modelValue }),
|
|
82
84
|
});
|
|
85
|
+
browserRuntime(app);
|
|
83
86
|
app.config.globalProperties.$t = (key: string) => key;
|
|
84
87
|
for (const name of ["UFileUpload", "ULink", "UTooltip"])
|
|
85
88
|
app.component(name, passthrough);
|
|
@@ -104,7 +107,8 @@ beforeEach(() => {
|
|
|
104
107
|
vi.stubGlobal("FORM_FIELD_LOADING_KEY", Symbol());
|
|
105
108
|
vi.stubGlobal("FORM_CONTENT_LANGUAGE_KEY", Symbol());
|
|
106
109
|
vi.stubGlobal("CONTENT_LANGUAGE_HEADER", "x-content-language");
|
|
107
|
-
vi.stubGlobal("
|
|
110
|
+
vi.stubGlobal("shallowReactive", shallowReactive);
|
|
111
|
+
stubApiData(authFetch);
|
|
108
112
|
vi.stubGlobal("useUploadWithProgress", () => ({
|
|
109
113
|
uploadWithProgress: vi.fn(),
|
|
110
114
|
}));
|