@antelopejs/dms 0.4.4 → 0.4.6
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 +43 -0
- package/dist/config.d.ts +1 -0
- package/dist/config.js.map +1 -1
- package/dist/hooks/invite-extensions.js +2 -2
- package/dist/hooks/invite-extensions.js.map +1 -1
- package/dist/implementations/dms/layout-banners.d.ts +18 -0
- package/dist/implementations/dms/layout-banners.js +60 -0
- package/dist/implementations/dms/layout-banners.js.map +1 -0
- package/dist/implementations/dms/page.d.ts +3 -0
- package/dist/implementations/dms/page.js +90 -28
- package/dist/implementations/dms/page.js.map +1 -1
- package/dist/implementations/dms/quick-actions.d.ts +5 -0
- package/dist/implementations/dms/quick-actions.js.map +1 -1
- package/dist/implementations/dms/resolver-timeout.d.ts +1 -0
- package/dist/implementations/dms/resolver-timeout.js +13 -0
- package/dist/implementations/dms/resolver-timeout.js.map +1 -0
- package/dist/implementations/dms/saas-mode.d.ts +8 -0
- package/dist/implementations/dms/saas-mode.js +20 -0
- package/dist/implementations/dms/saas-mode.js.map +1 -0
- package/dist/implementations/dms/stale-session.d.ts +2 -0
- package/dist/implementations/dms/stale-session.js +16 -0
- package/dist/implementations/dms/stale-session.js.map +1 -0
- package/dist/implementations/dms-auth/index.d.ts +3 -1
- package/dist/implementations/dms-auth/index.js +38 -8
- package/dist/implementations/dms-auth/index.js.map +1 -1
- package/dist/implementations/dms-html-render/service-jwt.js +2 -1
- package/dist/implementations/dms-html-render/service-jwt.js.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/pages/settings/users/index.d.ts +1 -0
- package/dist/pages/settings/users/index.js +1 -0
- package/dist/pages/settings/users/index.js.map +1 -1
- package/dist/pages/settings/users/invite-extension-routes.d.ts +4 -0
- package/dist/pages/settings/users/invite-extension-routes.js +57 -0
- package/dist/pages/settings/users/invite-extension-routes.js.map +1 -0
- package/dist/pages/settings/users/invites.d.ts +2 -1
- package/dist/pages/settings/users/invites.js +17 -5
- package/dist/pages/settings/users/invites.js.map +1 -1
- package/dist/pages/settings/users/members.d.ts +4 -1
- package/dist/pages/settings/users/members.js +10 -6
- package/dist/pages/settings/users/members.js.map +1 -1
- package/dist/pages/settings/users/notifications.js +1 -1
- package/dist/pages/settings/users/notifications.js.map +1 -1
- package/dist/pages/settings/users/quick-actions.d.ts +1 -0
- package/dist/pages/settings/users/quick-actions.js +22 -0
- package/dist/pages/settings/users/quick-actions.js.map +1 -0
- package/dist/routes/auth/client-ip.d.ts +7 -0
- package/dist/routes/auth/client-ip.js +42 -0
- package/dist/routes/auth/client-ip.js.map +1 -0
- package/dist/routes/auth/index.d.ts +1 -0
- package/dist/routes/auth/index.js +1 -0
- package/dist/routes/auth/index.js.map +1 -1
- package/dist/routes/auth/signup.d.ts +2 -0
- package/dist/routes/auth/signup.js +7 -2
- package/dist/routes/auth/signup.js.map +1 -1
- package/dist/routes/auth.d.ts +3 -0
- package/dist/routes/auth.js +15 -4
- package/dist/routes/auth.js.map +1 -1
- package/dist/test/integration/auth/client-ip.test.d.ts +1 -0
- package/dist/test/integration/auth/client-ip.test.js +14 -0
- package/dist/test/integration/auth/client-ip.test.js.map +1 -0
- package/dist/test/integration/invite-extension-edit.test.d.ts +1 -0
- package/dist/test/integration/invite-extension-edit.test.js +122 -0
- package/dist/test/integration/invite-extension-edit.test.js.map +1 -0
- package/dist/test/integration/page-session.test.d.ts +1 -0
- package/dist/test/integration/page-session.test.js +52 -0
- package/dist/test/integration/page-session.test.js.map +1 -0
- package/dist/test/integration/saas-mode-ownership.test.d.ts +1 -0
- package/dist/test/integration/saas-mode-ownership.test.js +81 -0
- package/dist/test/integration/saas-mode-ownership.test.js.map +1 -0
- package/dist/test/integration/tenant-lifecycle.test.js +38 -0
- package/dist/test/integration/tenant-lifecycle.test.js.map +1 -1
- package/dist/test/unit/client-ip.test.d.ts +1 -0
- package/dist/test/unit/client-ip.test.js +77 -0
- package/dist/test/unit/client-ip.test.js.map +1 -0
- package/dist/test/unit/implementations/dms/layout-banners.test.d.ts +1 -0
- package/dist/test/unit/implementations/dms/layout-banners.test.js +217 -0
- package/dist/test/unit/implementations/dms/layout-banners.test.js.map +1 -0
- package/dist/test/unit/implementations/dms/quick-actions-button.test.d.ts +1 -0
- package/dist/test/unit/implementations/dms/quick-actions-button.test.js +220 -0
- package/dist/test/unit/implementations/dms/quick-actions-button.test.js.map +1 -0
- package/dist/test/unit/implementations/dms-html-render/generate-html.test.js +1 -1
- package/dist/test/unit/implementations/dms-html-render/generate-html.test.js.map +1 -1
- package/dist/test/unit/implementations/dms-html-render/service-jwt.test.d.ts +1 -0
- package/dist/test/unit/implementations/dms-html-render/service-jwt.test.js +37 -0
- package/dist/test/unit/implementations/dms-html-render/service-jwt.test.js.map +1 -0
- package/dist/test/unit/interfaces/dms/invite-extension.test.js +138 -12
- package/dist/test/unit/interfaces/dms/invite-extension.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms/saas-mode.test.d.ts +1 -0
- package/dist/test/unit/interfaces/dms/saas-mode.test.js +42 -0
- package/dist/test/unit/interfaces/dms/saas-mode.test.js.map +1 -0
- package/dist/test/unit/interfaces/dms/tenant-invite-email.test.d.ts +1 -0
- package/dist/test/unit/interfaces/dms/tenant-invite-email.test.js +53 -0
- package/dist/test/unit/interfaces/dms/tenant-invite-email.test.js.map +1 -0
- package/dist/test/unit/interfaces/dms-base/table-view-form-slots.test.d.ts +1 -0
- package/dist/test/unit/interfaces/dms-base/table-view-form-slots.test.js +80 -0
- package/dist/test/unit/interfaces/dms-base/table-view-form-slots.test.js.map +1 -0
- package/dist/test/unit/interfaces/dms-base/table-view-tab-count-warning.test.d.ts +1 -0
- package/dist/test/unit/interfaces/dms-base/table-view-tab-count-warning.test.js +96 -0
- package/dist/test/unit/interfaces/dms-base/table-view-tab-count-warning.test.js.map +1 -0
- package/dist/test/unit/pages/settings/invite-member-quick-action.test.d.ts +1 -0
- package/dist/test/unit/pages/settings/invite-member-quick-action.test.js +104 -0
- package/dist/test/unit/pages/settings/invite-member-quick-action.test.js.map +1 -0
- package/dist/test/unit/pages/settings/notifications-access-gate.test.d.ts +1 -0
- package/dist/test/unit/pages/settings/notifications-access-gate.test.js +78 -0
- package/dist/test/unit/pages/settings/notifications-access-gate.test.js.map +1 -0
- package/dist/test/unit/signup-account-guard.test.js +20 -2
- package/dist/test/unit/signup-account-guard.test.js.map +1 -1
- package/dist/test/unit/utils/admin-invite-email.test.d.ts +1 -0
- package/dist/test/unit/utils/admin-invite-email.test.js +69 -0
- package/dist/test/unit/utils/admin-invite-email.test.js.map +1 -0
- package/dist/utils/admin-invite-email.d.ts +14 -0
- package/dist/utils/admin-invite-email.js +51 -0
- package/dist/utils/admin-invite-email.js.map +1 -0
- package/dist/utils/index.d.ts +0 -1
- package/dist/utils/index.js +0 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/validation/auth.schema.d.ts +3 -3
- package/dist/validation/auth.schema.js +1 -1
- package/dist/validation/auth.schema.js.map +1 -1
- package/frontend-vue/layers/dms-auth/app/custom-pages/auth/signup.vue +37 -8
- package/frontend-vue/layers/dms-auth/app/middleware/auth.ts +5 -2
- package/frontend-vue/layers/dms-auth/i18n/locales/auth-en-GB.json +3 -1
- package/frontend-vue/layers/dms-auth/i18n/locales/auth-fr-FR.json +3 -1
- package/frontend-vue/layers/dms-core/app/composables/useApiError.ts +27 -2
- package/frontend-vue/layers/dms-core/app/utils/menu.ts +26 -10
- package/frontend-vue/layers/dms-core/app/utils/routePath.ts +23 -0
- package/frontend-vue/layers/dms-core/i18n/locales/core-en-GB.json +3 -0
- package/frontend-vue/layers/dms-core/i18n/locales/core-fr-FR.json +3 -0
- package/frontend-vue/layers/dms-core/shared/types/runtime-config.d.ts +4 -1
- package/frontend-vue/layers/dms-layout/app/app.config.ts +9 -1
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardBanners.vue +50 -0
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardHeader.vue +16 -13
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardSidebar.vue +2 -2
- package/frontend-vue/layers/dms-layout/app/build/components/notification/NotificationPopover.vue +11 -5
- package/frontend-vue/layers/dms-layout/app/build/components/notification/widgetRequest.ts +20 -0
- package/frontend-vue/layers/dms-layout/app/components/navigation/CollapsedMenuItem.vue +0 -5
- package/frontend-vue/layers/dms-layout/app/components/profile/ProfileLanguage.vue +3 -23
- package/frontend-vue/layers/dms-layout/app/composables/layout/useLayoutBanners.ts +59 -0
- package/frontend-vue/layers/dms-layout/app/composables/page/useQuickActions.ts +15 -2
- package/frontend-vue/layers/dms-layout/app/composables/profile/useUserLanguage.ts +33 -0
- package/frontend-vue/layers/dms-layout/app/custom-layouts/DefaultLayout.vue +2 -0
- package/frontend-vue/layers/dms-layout/app/emails/EmailAdminInvite.vue +54 -9
- package/frontend-vue/layers/dms-layout/app/error.vue +17 -2
- package/frontend-vue/layers/dms-layout/app/plugins/command-palette-session.client.ts +2 -1
- package/frontend-vue/layers/dms-layout/app/types/page.ts +22 -0
- package/frontend-vue/layers/dms-layout/app/utils/breadcrumb.ts +48 -0
- package/frontend-vue/layers/dms-layout/app/utils/favorites.ts +94 -0
- package/frontend-vue/layers/dms-layout/i18n/locales/layout-en-GB.json +21 -2
- package/frontend-vue/layers/dms-layout/i18n/locales/layout-fr-FR.json +21 -2
- package/frontend-vue/layers/dms-onboarding/app/composables/onboarding/index.ts +1 -1
- package/frontend-vue/layers/dms-onboarding/app/composables/onboarding/{useOnboardingMiddleware.ts → useOnboarding.ts} +0 -12
- package/frontend-vue/layers/dms-onboarding/app/custom-pages/onboarding.vue +4 -1
- package/frontend-vue/layers/dms-onboarding/app/middleware/onboarding.global.ts +21 -4
- package/frontend-vue/layers/dms-ui/app/build/components/containers/drawer/DynamicDrawer.vue +13 -3
- package/frontend-vue/layers/dms-ui/app/build/components/containers/modal/DynamicModal.vue +13 -3
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerDefaults.ts +31 -3
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationBadges.ts +36 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViewConfig.ts +2 -7
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/columnVisibility.ts +13 -0
- package/frontend-vue/layers/dms-ui/app/build/types/tree.ts +5 -10
- package/frontend-vue/layers/dms-ui/app/components/PasswordStrength.vue +1 -1
- package/frontend-vue/layers/dms-ui/app/components/activity/ActivityItem.vue +1 -1
- package/frontend-vue/layers/dms-ui/app/components/banner/Banner.vue +1 -1
- package/frontend-vue/layers/dms-ui/app/components/card/NavCard.vue +2 -2
- package/frontend-vue/layers/dms-ui/app/components/chart/Chart.vue +6 -16
- package/frontend-vue/layers/dms-ui/app/components/chart/ChartCard.vue +2 -2
- package/frontend-vue/layers/dms-ui/app/components/chart/internal/TrendBadge.vue +1 -1
- package/frontend-vue/layers/dms-ui/app/components/copy/CopyButton.vue +1 -1
- package/frontend-vue/layers/dms-ui/app/components/kpi/KpiCard.vue +3 -3
- package/frontend-vue/layers/dms-ui/app/components/master-detail/MasterDetail.vue +5 -1
- package/frontend-vue/layers/dms-ui/app/components/segmented/Segmented.vue +1 -1
- package/frontend-vue/layers/dms-ui/app/components/status/StatusSummary.vue +1 -1
- package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +33 -10
- package/frontend-vue/layers/dms-ui/app/composables/chart/donutRecords.ts +26 -0
- package/frontend-vue/layers/dms-ui/app/composables/tab/types/props.ts +4 -2
- package/frontend-vue/layers/dms-ui/app/composables/tab/useTab.ts +3 -3
- package/frontend-vue/layers/dms-ui/app/composables/table-view/types/column.ts +1 -1
- package/frontend-vue/layers/dms-ui/app/composables/table-view/types/custom-button.ts +2 -0
- package/frontend-vue/layers/dms-ui/app/types/quick-actions.ts +38 -3
- package/frontend-vue/tests/admin-invite-email.test.ts +86 -0
- package/frontend-vue/tests/api-error-title.test.ts +24 -0
- package/frontend-vue/tests/auth-middleware-error.test.ts +58 -0
- package/frontend-vue/tests/breadcrumb-target.test.ts +50 -0
- package/frontend-vue/tests/button-disabled-theme.test.ts +21 -0
- package/frontend-vue/tests/chart-nested-donut.test.ts +97 -0
- package/frontend-vue/tests/error-page-message.test.ts +83 -0
- package/frontend-vue/tests/favorite-page.test.ts +104 -0
- package/frontend-vue/tests/icon-aria-hidden.test.ts +29 -0
- package/frontend-vue/tests/layout-banners.test.ts +224 -0
- package/frontend-vue/tests/menu-navigation.test.ts +39 -10
- package/frontend-vue/tests/notification-popover-errors.test.ts +147 -0
- package/frontend-vue/tests/onboarding-middleware.test.ts +103 -0
- package/frontend-vue/tests/password-strength-meter.test.ts +33 -0
- package/frontend-vue/tests/quick-action-intent.test.ts +74 -0
- package/frontend-vue/tests/quick-actions.test.ts +23 -0
- package/frontend-vue/tests/relation-badges.test.ts +32 -0
- package/frontend-vue/tests/signup-invitation-link.test.ts +126 -0
- package/frontend-vue/tests/skeleton-paragraph.test.ts +30 -0
- package/frontend-vue/tests/tab-without-component-id.test.ts +51 -0
- package/frontend-vue/tests/table-column-visibility.test.ts +20 -0
- package/frontend-vue/tests/user-language.test.ts +58 -0
- package/frontend-vue/tests/vue-inventory.test.ts +1 -1
- package/package.json +2 -2
- package/skills/dms/REFERENCE.md +1 -1
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import {
|
|
3
|
+
computed,
|
|
4
|
+
createApp,
|
|
5
|
+
createSSRApp,
|
|
6
|
+
defineComponent,
|
|
7
|
+
h,
|
|
8
|
+
nextTick,
|
|
9
|
+
ref,
|
|
10
|
+
type App,
|
|
11
|
+
type Ref,
|
|
12
|
+
} from "vue";
|
|
13
|
+
import { renderToString } from "vue/server-renderer";
|
|
14
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
15
|
+
import DashboardBanners from "../layers/dms-layout/app/build/components/layout/DashboardBanners.vue";
|
|
16
|
+
import {
|
|
17
|
+
resolveLayoutBannerPresentation,
|
|
18
|
+
useLayoutBanners,
|
|
19
|
+
} from "../layers/dms-layout/app/composables/layout/useLayoutBanners";
|
|
20
|
+
import type {
|
|
21
|
+
LayoutBanner,
|
|
22
|
+
LayoutBannerVariant,
|
|
23
|
+
SiteLayout,
|
|
24
|
+
} from "../layers/dms-layout/app/types/page";
|
|
25
|
+
|
|
26
|
+
const DISMISSED_BANNERS_COOKIE = "dms-dismissed-banners";
|
|
27
|
+
|
|
28
|
+
const siteLayout = ref<SiteLayout | undefined>();
|
|
29
|
+
let dismissedCookie: Ref<string[]>;
|
|
30
|
+
const cookieNames: string[] = [];
|
|
31
|
+
|
|
32
|
+
const banner = (extra: Partial<LayoutBanner> = {}): LayoutBanner => ({
|
|
33
|
+
key: "banner",
|
|
34
|
+
variant: "info",
|
|
35
|
+
order: 0,
|
|
36
|
+
dismissible: false,
|
|
37
|
+
text: "Heads up",
|
|
38
|
+
...extra,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
function serveBanners(banners: LayoutBanner[] | undefined): void {
|
|
42
|
+
siteLayout.value = { pages: {}, categories: {}, banners };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Renders what the real alert exposes to the banner: its description slot and
|
|
46
|
+
// a close button that reports the alert closed.
|
|
47
|
+
const AlertStub = defineComponent({
|
|
48
|
+
props: {
|
|
49
|
+
color: { type: String, default: undefined },
|
|
50
|
+
icon: { type: String, default: undefined },
|
|
51
|
+
close: { type: Boolean, default: false },
|
|
52
|
+
},
|
|
53
|
+
emits: ["update:open"],
|
|
54
|
+
setup:
|
|
55
|
+
(props, { slots, emit }) =>
|
|
56
|
+
() =>
|
|
57
|
+
h("div", { "data-color": props.color, "data-icon": props.icon }, [
|
|
58
|
+
slots.description?.(),
|
|
59
|
+
props.close
|
|
60
|
+
? h("button", { onClick: () => emit("update:open", false) }, "close")
|
|
61
|
+
: null,
|
|
62
|
+
]),
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const BannerContent = defineComponent({
|
|
66
|
+
props: { days: { type: Number, required: true } },
|
|
67
|
+
setup: (props) => () => h("strong", `${props.days} days left`),
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
function mountable(root: App): App {
|
|
71
|
+
root.component("UAlert", AlertStub);
|
|
72
|
+
root.component("BannerContent", BannerContent);
|
|
73
|
+
return root;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
beforeEach(() => {
|
|
77
|
+
siteLayout.value = undefined;
|
|
78
|
+
cookieNames.length = 0;
|
|
79
|
+
dismissedCookie = ref<string[]>([]);
|
|
80
|
+
vi.stubGlobal("computed", computed);
|
|
81
|
+
vi.stubGlobal("useSiteLayout", () => ({ siteLayout }));
|
|
82
|
+
vi.stubGlobal("useDmsCookie", (name: string) => {
|
|
83
|
+
cookieNames.push(name);
|
|
84
|
+
return dismissedCookie;
|
|
85
|
+
});
|
|
86
|
+
vi.stubGlobal("useTranslation", () => ({
|
|
87
|
+
processI18n: (key: string) => key.replace(/^\$/, "t:"),
|
|
88
|
+
}));
|
|
89
|
+
vi.stubGlobal("useLayoutBanners", useLayoutBanners);
|
|
90
|
+
vi.stubGlobal(
|
|
91
|
+
"resolveLayoutBannerPresentation",
|
|
92
|
+
resolveLayoutBannerPresentation,
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
afterEach(() => {
|
|
97
|
+
vi.unstubAllGlobals();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe("resolveLayoutBannerPresentation", () => {
|
|
101
|
+
it.each([
|
|
102
|
+
["info", "status"],
|
|
103
|
+
["warning", "status"],
|
|
104
|
+
["error", "alert"],
|
|
105
|
+
] as const)("announces a %s banner as %s", (variant, role) => {
|
|
106
|
+
const presentation = resolveLayoutBannerPresentation(banner({ variant }));
|
|
107
|
+
expect(presentation.role).toBe(role);
|
|
108
|
+
expect(presentation.color).toBe(variant);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("renders an unknown variant as info rather than unstyled", () => {
|
|
112
|
+
const presentation = resolveLayoutBannerPresentation(
|
|
113
|
+
banner({ variant: "success" as LayoutBannerVariant }),
|
|
114
|
+
);
|
|
115
|
+
expect(presentation).toEqual(
|
|
116
|
+
resolveLayoutBannerPresentation(banner({ variant: "info" })),
|
|
117
|
+
);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("keeps the banner's own icon over the variant's", () => {
|
|
121
|
+
expect(
|
|
122
|
+
resolveLayoutBannerPresentation(banner({ icon: "i-ph-clock" })).icon,
|
|
123
|
+
).toBe("i-ph-clock");
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe("useLayoutBanners", () => {
|
|
128
|
+
it("serves nothing for a backend that predates banners", () => {
|
|
129
|
+
serveBanners(undefined);
|
|
130
|
+
expect(useLayoutBanners().banners.value).toEqual([]);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("drops a dismissed banner, and remembers the dismissal in the cookie", () => {
|
|
134
|
+
serveBanners([
|
|
135
|
+
banner({ key: "a", dismissible: true }),
|
|
136
|
+
banner({ key: "b", dismissible: true }),
|
|
137
|
+
]);
|
|
138
|
+
const { banners, dismiss } = useLayoutBanners();
|
|
139
|
+
|
|
140
|
+
dismiss("a");
|
|
141
|
+
dismiss("a");
|
|
142
|
+
|
|
143
|
+
expect(cookieNames).toEqual([DISMISSED_BANNERS_COOKIE]);
|
|
144
|
+
expect(dismissedCookie.value).toEqual(["a"]);
|
|
145
|
+
expect(banners.value.map((entry) => entry.key)).toEqual(["b"]);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// A banner that turned non-dismissible (an overdue invoice) must show again
|
|
149
|
+
// even to a user who closed its dismissible predecessor under the same key.
|
|
150
|
+
it("ignores a dismissal for a banner that is not dismissible", () => {
|
|
151
|
+
dismissedCookie.value = ["a"];
|
|
152
|
+
serveBanners([banner({ key: "a", dismissible: false })]);
|
|
153
|
+
expect(useLayoutBanners().banners.value).toHaveLength(1);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
describe("DashboardBanners", () => {
|
|
158
|
+
it("server-renders the banners in order, each with its variant's role", async () => {
|
|
159
|
+
serveBanners([
|
|
160
|
+
banner({ key: "billing", variant: "error", text: "$billing.overdue" }),
|
|
161
|
+
banner({
|
|
162
|
+
key: "trial",
|
|
163
|
+
variant: "warning",
|
|
164
|
+
component: "BannerContent",
|
|
165
|
+
props: { days: 3 },
|
|
166
|
+
text: undefined,
|
|
167
|
+
}),
|
|
168
|
+
banner({ key: "news", variant: "info", text: "New release" }),
|
|
169
|
+
]);
|
|
170
|
+
|
|
171
|
+
const html = await renderToString(
|
|
172
|
+
mountable(createSSRApp(DashboardBanners)),
|
|
173
|
+
);
|
|
174
|
+
const document = new DOMParser().parseFromString(html, "text/html");
|
|
175
|
+
const rendered = [
|
|
176
|
+
...document.querySelectorAll("[data-dms-layout-banner]"),
|
|
177
|
+
].map((node) => ({
|
|
178
|
+
key: node.getAttribute("data-dms-layout-banner"),
|
|
179
|
+
role: node.getAttribute("role"),
|
|
180
|
+
text: node.textContent,
|
|
181
|
+
}));
|
|
182
|
+
|
|
183
|
+
expect(rendered).toEqual([
|
|
184
|
+
{ key: "billing", role: "alert", text: "t:billing.overdue" },
|
|
185
|
+
{ key: "trial", role: "status", text: "3 days left" },
|
|
186
|
+
{ key: "news", role: "status", text: "New release" },
|
|
187
|
+
]);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// The server reads the same cookie, so a dismissed banner never reaches the
|
|
191
|
+
// page only to be removed on hydration.
|
|
192
|
+
it("leaves a dismissed banner out of the server-rendered page", async () => {
|
|
193
|
+
dismissedCookie.value = ["news"];
|
|
194
|
+
serveBanners([banner({ key: "news", dismissible: true })]);
|
|
195
|
+
|
|
196
|
+
const html = await renderToString(
|
|
197
|
+
mountable(createSSRApp(DashboardBanners)),
|
|
198
|
+
);
|
|
199
|
+
expect(html).not.toContain("data-dms-layout-banners");
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it("offers a close button only on a dismissible banner, and closes it", async () => {
|
|
203
|
+
serveBanners([
|
|
204
|
+
banner({ key: "sticky" }),
|
|
205
|
+
banner({ key: "closable", dismissible: true }),
|
|
206
|
+
]);
|
|
207
|
+
const host = document.createElement("div");
|
|
208
|
+
const app = mountable(createApp(DashboardBanners));
|
|
209
|
+
app.mount(host);
|
|
210
|
+
|
|
211
|
+
const buttons = host.querySelectorAll("button");
|
|
212
|
+
expect(buttons).toHaveLength(1);
|
|
213
|
+
buttons[0]!.click();
|
|
214
|
+
await nextTick();
|
|
215
|
+
|
|
216
|
+
expect(dismissedCookie.value).toEqual(["closable"]);
|
|
217
|
+
expect(
|
|
218
|
+
[...host.querySelectorAll("[data-dms-layout-banner]")].map((node) =>
|
|
219
|
+
node.getAttribute("data-dms-layout-banner"),
|
|
220
|
+
),
|
|
221
|
+
).toEqual(["sticky"]);
|
|
222
|
+
app.unmount();
|
|
223
|
+
});
|
|
224
|
+
});
|
|
@@ -29,10 +29,16 @@ describe("buildMenuItemTarget", () => {
|
|
|
29
29
|
expect(buildMenuItemTarget(node({}))).to.equal("/project");
|
|
30
30
|
});
|
|
31
31
|
|
|
32
|
-
it("returns a
|
|
32
|
+
it("returns a string link carrying the query the entry declares", () => {
|
|
33
33
|
expect(
|
|
34
34
|
buildMenuItemTarget(node({ query: { project: "invoicer" } })),
|
|
35
|
-
).to.
|
|
35
|
+
).to.equal("/project?project=invoicer");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("encodes the query values of the link", () => {
|
|
39
|
+
expect(
|
|
40
|
+
buildMenuItemTarget(node({ query: { project: "a b&c", env: "prod" } })),
|
|
41
|
+
).to.equal("/project?project=a+b%26c&env=prod");
|
|
36
42
|
});
|
|
37
43
|
|
|
38
44
|
it("returns no target for a category that has no page behind it", () => {
|
|
@@ -45,11 +51,11 @@ describe("buildMenuItemTarget", () => {
|
|
|
45
51
|
describe("isMenuItemActive with query entries", () => {
|
|
46
52
|
const invoicer = {
|
|
47
53
|
fullId: "cloud.projects.invoicer",
|
|
48
|
-
to:
|
|
54
|
+
to: "/project?project=invoicer",
|
|
49
55
|
};
|
|
50
56
|
const resto = {
|
|
51
57
|
fullId: "cloud.projects.resto",
|
|
52
|
-
to:
|
|
58
|
+
to: "/project?project=resto",
|
|
53
59
|
};
|
|
54
60
|
|
|
55
61
|
it("highlights only the entry matching the current query", () => {
|
|
@@ -63,6 +69,22 @@ describe("isMenuItemActive with query entries", () => {
|
|
|
63
69
|
expect(isMenuItemActive(invoicer, null, current)).to.equal(true);
|
|
64
70
|
});
|
|
65
71
|
|
|
72
|
+
it("tells apart entries whose query values share a prefix", () => {
|
|
73
|
+
const current = route("/project", { project: "invoicer-2" });
|
|
74
|
+
expect(isMenuItemActive(invoicer, null, current)).to.equal(false);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("stays active on the entry's sub-routes carrying its query", () => {
|
|
78
|
+
const current = route("/project/settings", { project: "invoicer" });
|
|
79
|
+
expect(isMenuItemActive(invoicer, null, current)).to.equal(true);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("still accepts an object target", () => {
|
|
83
|
+
const entry = { to: { path: "/project", query: { project: "invoicer" } } };
|
|
84
|
+
const current = route("/project", { project: "invoicer" });
|
|
85
|
+
expect(isMenuItemActive(entry, null, current)).to.equal(true);
|
|
86
|
+
});
|
|
87
|
+
|
|
66
88
|
it("stays inactive on the same path without the entry's query", () => {
|
|
67
89
|
expect(isMenuItemActive(invoicer, null, route("/project"))).to.equal(false);
|
|
68
90
|
});
|
|
@@ -86,11 +108,8 @@ describe("addActiveStateToMenuItems", () => {
|
|
|
86
108
|
it("marks a single query entry active across a group", () => {
|
|
87
109
|
const groups = [
|
|
88
110
|
[
|
|
89
|
-
{
|
|
90
|
-
|
|
91
|
-
to: { path: "/project", query: { project: "invoicer" } },
|
|
92
|
-
},
|
|
93
|
-
{ id: "resto", to: { path: "/project", query: { project: "resto" } } },
|
|
111
|
+
{ id: "invoicer", to: "/project?project=invoicer" },
|
|
112
|
+
{ id: "resto", to: "/project?project=resto" },
|
|
94
113
|
],
|
|
95
114
|
];
|
|
96
115
|
|
|
@@ -100,7 +119,17 @@ describe("addActiveStateToMenuItems", () => {
|
|
|
100
119
|
route("/project", { project: "resto" }),
|
|
101
120
|
);
|
|
102
121
|
|
|
103
|
-
expect(group?.[0]?.active).to.equal(
|
|
122
|
+
expect(group?.[0]?.active).to.equal(false);
|
|
104
123
|
expect(group?.[1]?.active).to.equal(true);
|
|
105
124
|
});
|
|
125
|
+
|
|
126
|
+
it("leaves the native highlighting of an inactive plain entry alone", () => {
|
|
127
|
+
const [group] = addActiveStateToMenuItems(
|
|
128
|
+
[[{ id: "orders", to: "/orders" }]],
|
|
129
|
+
null,
|
|
130
|
+
route("/invoices"),
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
expect(group?.[0]?.active).to.equal(undefined);
|
|
134
|
+
});
|
|
106
135
|
});
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { afterEach, beforeEach, expect, it, vi } from "vitest";
|
|
3
|
+
import {
|
|
4
|
+
createApp,
|
|
5
|
+
defineComponent,
|
|
6
|
+
h,
|
|
7
|
+
nextTick,
|
|
8
|
+
onMounted,
|
|
9
|
+
onUnmounted,
|
|
10
|
+
ref,
|
|
11
|
+
watch,
|
|
12
|
+
type App,
|
|
13
|
+
} from "vue";
|
|
14
|
+
import NotificationPopover from "../layers/dms-layout/app/build/components/notification/NotificationPopover.vue";
|
|
15
|
+
|
|
16
|
+
const HTTP_FORBIDDEN = 403;
|
|
17
|
+
const UNREAD_COUNT = 4;
|
|
18
|
+
|
|
19
|
+
interface NotificationsStub {
|
|
20
|
+
unreadCount: ReturnType<typeof ref<number>>;
|
|
21
|
+
fetchUnreadCount: ReturnType<typeof vi.fn>;
|
|
22
|
+
fetchNotifications: ReturnType<typeof vi.fn>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let app: App;
|
|
26
|
+
let host: HTMLDivElement;
|
|
27
|
+
let errorHandler: ReturnType<typeof vi.fn>;
|
|
28
|
+
let notifications: NotificationsStub;
|
|
29
|
+
|
|
30
|
+
function forbidden(): Promise<never> {
|
|
31
|
+
return Promise.reject(
|
|
32
|
+
Object.assign(new Error("tenant.suspended"), {
|
|
33
|
+
response: { status: HTTP_FORBIDDEN },
|
|
34
|
+
}),
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const Popover = defineComponent({
|
|
39
|
+
props: { open: { type: Boolean, default: false } },
|
|
40
|
+
emits: ["update:open"],
|
|
41
|
+
setup:
|
|
42
|
+
(props, { emit, slots }) =>
|
|
43
|
+
() =>
|
|
44
|
+
h("div", [
|
|
45
|
+
h(
|
|
46
|
+
"div",
|
|
47
|
+
{ "data-trigger": "", onClick: () => emit("update:open", true) },
|
|
48
|
+
slots.default?.(),
|
|
49
|
+
),
|
|
50
|
+
props.open ? slots.content?.() : null,
|
|
51
|
+
]),
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const Passthrough = defineComponent({
|
|
55
|
+
setup:
|
|
56
|
+
(_, { slots }) =>
|
|
57
|
+
() =>
|
|
58
|
+
h("div", slots.default?.()),
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
function installRuntime(): void {
|
|
62
|
+
Object.entries({ ref, watch, nextTick, onMounted, onUnmounted }).forEach(
|
|
63
|
+
([key, value]) => vi.stubGlobal(key, value),
|
|
64
|
+
);
|
|
65
|
+
vi.stubGlobal("useI18n", () => ({ t: (key: string) => key, locale: "en" }));
|
|
66
|
+
vi.stubGlobal("useUserSession", () => ({ loggedIn: ref(true) }));
|
|
67
|
+
vi.stubGlobal("useTranslation", () => ({
|
|
68
|
+
processI18n: (value: string) => value,
|
|
69
|
+
}));
|
|
70
|
+
vi.stubGlobal("useNotifications", () => ({
|
|
71
|
+
...notifications,
|
|
72
|
+
notifications: ref([]),
|
|
73
|
+
hasMore: ref(false),
|
|
74
|
+
markAllAsRead: vi.fn(),
|
|
75
|
+
}));
|
|
76
|
+
vi.stubGlobal("useInfiniteScroll", () => ({
|
|
77
|
+
isLoadingMore: ref(false),
|
|
78
|
+
setupObserver: vi.fn(),
|
|
79
|
+
disconnectObserver: vi.fn(),
|
|
80
|
+
}));
|
|
81
|
+
vi.stubGlobal("NotificationEvents", { NOTIFICATION_RECEIVED: "received" });
|
|
82
|
+
vi.stubGlobal("FormEvents", { SUBMIT_SUCCESS: "submit-success" });
|
|
83
|
+
vi.stubGlobal("navigateDms", vi.fn());
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function mountPopover(): Promise<void> {
|
|
87
|
+
app = createApp(NotificationPopover);
|
|
88
|
+
app.config.errorHandler = errorHandler;
|
|
89
|
+
app.config.globalProperties.$t = (key: string) => key;
|
|
90
|
+
app.component("UPopover", Popover);
|
|
91
|
+
app.component("UButton", Passthrough);
|
|
92
|
+
app.component("USeparator", Passthrough);
|
|
93
|
+
app.component("UIcon", Passthrough);
|
|
94
|
+
app.mount(host);
|
|
95
|
+
await vi.waitFor(() =>
|
|
96
|
+
expect(notifications.fetchUnreadCount).toHaveBeenCalled(),
|
|
97
|
+
);
|
|
98
|
+
await nextTick();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
beforeEach(() => {
|
|
102
|
+
errorHandler = vi.fn();
|
|
103
|
+
notifications = {
|
|
104
|
+
unreadCount: ref(0),
|
|
105
|
+
fetchUnreadCount: vi.fn(),
|
|
106
|
+
fetchNotifications: vi.fn(),
|
|
107
|
+
};
|
|
108
|
+
vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
109
|
+
installRuntime();
|
|
110
|
+
host = document.createElement("div");
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
afterEach(() => {
|
|
114
|
+
app?.unmount();
|
|
115
|
+
vi.unstubAllGlobals();
|
|
116
|
+
vi.restoreAllMocks();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("shows the unread badge when the count loads", async () => {
|
|
120
|
+
notifications.fetchUnreadCount.mockImplementation(async () => {
|
|
121
|
+
notifications.unreadCount.value = UNREAD_COUNT;
|
|
122
|
+
});
|
|
123
|
+
await mountPopover();
|
|
124
|
+
expect(host.textContent).toContain(String(UNREAD_COUNT));
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("hides the badge instead of failing the page when the count is refused", async () => {
|
|
128
|
+
notifications.unreadCount.value = UNREAD_COUNT;
|
|
129
|
+
notifications.fetchUnreadCount.mockImplementation(forbidden);
|
|
130
|
+
await mountPopover();
|
|
131
|
+
await vi.waitFor(() => expect(console.warn).toHaveBeenCalled());
|
|
132
|
+
expect(errorHandler).not.toHaveBeenCalled();
|
|
133
|
+
expect(notifications.unreadCount.value).toBe(0);
|
|
134
|
+
expect(host.textContent).not.toContain(String(UNREAD_COUNT));
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("keeps the page alive when the notification list is refused on open", async () => {
|
|
138
|
+
notifications.fetchNotifications.mockImplementation(forbidden);
|
|
139
|
+
await mountPopover();
|
|
140
|
+
host.querySelector<HTMLElement>("[data-trigger]")!.click();
|
|
141
|
+
await vi.waitFor(() =>
|
|
142
|
+
expect(notifications.fetchNotifications).toHaveBeenCalled(),
|
|
143
|
+
);
|
|
144
|
+
await vi.waitFor(() => expect(console.warn).toHaveBeenCalled());
|
|
145
|
+
expect(errorHandler).not.toHaveBeenCalled();
|
|
146
|
+
expect(host.textContent).toContain("notification.dropdown.no_notifications");
|
|
147
|
+
});
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
|
|
4
|
+
type Middleware = (to: { path: string }) => Promise<unknown>;
|
|
5
|
+
|
|
6
|
+
const navigateDms = vi.fn();
|
|
7
|
+
const onboarding = vi.fn();
|
|
8
|
+
const session = { loggedIn: { value: false } };
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
vi.resetModules();
|
|
12
|
+
vi.stubGlobal("defineDmsMiddleware", (handler: Middleware) => handler);
|
|
13
|
+
vi.stubGlobal("navigateDms", navigateDms);
|
|
14
|
+
vi.stubGlobal("useOnboarding", onboarding);
|
|
15
|
+
vi.stubGlobal("useUserSession", () => session);
|
|
16
|
+
vi.stubGlobal("useHomepage", () => "/dashboard");
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
session.loggedIn.value = false;
|
|
21
|
+
navigateDms.mockReset();
|
|
22
|
+
onboarding.mockReset();
|
|
23
|
+
vi.unstubAllGlobals();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
async function loadMiddleware(): Promise<Middleware> {
|
|
27
|
+
const module = await import(
|
|
28
|
+
"../layers/dms-onboarding/app/middleware/onboarding.global"
|
|
29
|
+
);
|
|
30
|
+
return module.default as unknown as Middleware;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe("onboarding middleware", () => {
|
|
34
|
+
it("sends every route to the wizard while no admin exists", async () => {
|
|
35
|
+
onboarding.mockResolvedValue({ hasOnboarded: false });
|
|
36
|
+
const middleware = await loadMiddleware();
|
|
37
|
+
|
|
38
|
+
await middleware({ path: "/settings" });
|
|
39
|
+
|
|
40
|
+
expect(navigateDms).toHaveBeenCalledWith("/onboarding");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("lets the wizard render while no admin exists", async () => {
|
|
44
|
+
onboarding.mockResolvedValue({ hasOnboarded: false });
|
|
45
|
+
const middleware = await loadMiddleware();
|
|
46
|
+
|
|
47
|
+
await middleware({ path: "/onboarding" });
|
|
48
|
+
|
|
49
|
+
expect(navigateDms).not.toHaveBeenCalled();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("sends a signed-in user away from the finished wizard to the homepage", async () => {
|
|
53
|
+
onboarding.mockResolvedValue({ hasOnboarded: true });
|
|
54
|
+
session.loggedIn.value = true;
|
|
55
|
+
const middleware = await loadMiddleware();
|
|
56
|
+
|
|
57
|
+
await middleware({ path: "/onboarding" });
|
|
58
|
+
|
|
59
|
+
expect(navigateDms).toHaveBeenCalledWith("/dashboard", { replace: true });
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("sends an anonymous visitor away from the finished wizard to the login page", async () => {
|
|
63
|
+
onboarding.mockResolvedValue({ hasOnboarded: true });
|
|
64
|
+
const middleware = await loadMiddleware();
|
|
65
|
+
|
|
66
|
+
await middleware({ path: "/onboarding" });
|
|
67
|
+
|
|
68
|
+
expect(navigateDms).toHaveBeenCalledWith("/auth", { replace: true });
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("leaves other routes alone once onboarding is done", async () => {
|
|
72
|
+
onboarding.mockResolvedValue({ hasOnboarded: true });
|
|
73
|
+
const middleware = await loadMiddleware();
|
|
74
|
+
|
|
75
|
+
await middleware({ path: "/settings" });
|
|
76
|
+
|
|
77
|
+
expect(navigateDms).not.toHaveBeenCalled();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("does not block navigation when the onboarding status is unavailable", async () => {
|
|
81
|
+
onboarding.mockRejectedValue(new Error("offline"));
|
|
82
|
+
const middleware = await loadMiddleware();
|
|
83
|
+
|
|
84
|
+
await middleware({ path: "/onboarding" });
|
|
85
|
+
|
|
86
|
+
expect(navigateDms).not.toHaveBeenCalled();
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe("onboarding page", () => {
|
|
91
|
+
// Global middleware runs on a direct load only for a page declaring its
|
|
92
|
+
// meta: without it, a finished wizard would render instead of redirecting.
|
|
93
|
+
it("declares its meta so the onboarding middleware runs on a direct load", () => {
|
|
94
|
+
const page = readFileSync(
|
|
95
|
+
new URL(
|
|
96
|
+
"../layers/dms-onboarding/app/custom-pages/onboarding.vue",
|
|
97
|
+
import.meta.url,
|
|
98
|
+
),
|
|
99
|
+
"utf8",
|
|
100
|
+
);
|
|
101
|
+
expect(page).toMatch(/defineDmsPageMeta\(/);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { afterEach, expect, it } from "vitest";
|
|
3
|
+
import { createApp, defineComponent, h, type App } from "vue";
|
|
4
|
+
import PasswordStrength from "../layers/dms-ui/app/components/PasswordStrength.vue";
|
|
5
|
+
|
|
6
|
+
let app: App | undefined;
|
|
7
|
+
|
|
8
|
+
afterEach(() => app?.unmount());
|
|
9
|
+
|
|
10
|
+
it("scales the meter to the number of requirements", () => {
|
|
11
|
+
const host = document.createElement("div");
|
|
12
|
+
const strength = Array.from({ length: 5 }, (_, index) => ({
|
|
13
|
+
met: true,
|
|
14
|
+
text: `requirement ${index}`,
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
app = createApp(PasswordStrength, { color: "success", score: 5, strength });
|
|
18
|
+
app.config.globalProperties.$t = (key: string) => key;
|
|
19
|
+
app.component(
|
|
20
|
+
"UProgress",
|
|
21
|
+
defineComponent({
|
|
22
|
+
props: { modelValue: Number, max: Number },
|
|
23
|
+
setup: (props) => () =>
|
|
24
|
+
h("progress", { value: props.modelValue, max: props.max }),
|
|
25
|
+
}),
|
|
26
|
+
);
|
|
27
|
+
app.component("UIcon", defineComponent({ render: () => null }));
|
|
28
|
+
app.mount(host);
|
|
29
|
+
|
|
30
|
+
const meter = host.querySelector("progress")!;
|
|
31
|
+
expect(meter.getAttribute("max")).toBe("5");
|
|
32
|
+
expect(meter.getAttribute("value")).toBe("5");
|
|
33
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
QUICK_ACTION_ADD,
|
|
4
|
+
QUICK_ACTION_BUTTON,
|
|
5
|
+
QUICK_ACTION_BUTTON_KEY,
|
|
6
|
+
QUICK_ACTION_COMPONENT_KEY,
|
|
7
|
+
QUICK_ACTION_QUERY_KEY,
|
|
8
|
+
readQuickActionIntent,
|
|
9
|
+
} from "../layers/dms-ui/app/types/quick-actions";
|
|
10
|
+
|
|
11
|
+
const COMPONENT_ID = "table";
|
|
12
|
+
|
|
13
|
+
describe("readQuickActionIntent", () => {
|
|
14
|
+
it("reads an open-form intent addressed to the table view", () => {
|
|
15
|
+
const intent = readQuickActionIntent(
|
|
16
|
+
{
|
|
17
|
+
[QUICK_ACTION_QUERY_KEY]: QUICK_ACTION_ADD,
|
|
18
|
+
[QUICK_ACTION_COMPONENT_KEY]: COMPONENT_ID,
|
|
19
|
+
},
|
|
20
|
+
COMPONENT_ID,
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
expect(intent).toEqual({ kind: "add" });
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("reads which button a button intent presses", () => {
|
|
27
|
+
const intent = readQuickActionIntent(
|
|
28
|
+
{
|
|
29
|
+
[QUICK_ACTION_QUERY_KEY]: QUICK_ACTION_BUTTON,
|
|
30
|
+
[QUICK_ACTION_COMPONENT_KEY]: COMPONENT_ID,
|
|
31
|
+
[QUICK_ACTION_BUTTON_KEY]: "invite",
|
|
32
|
+
},
|
|
33
|
+
COMPONENT_ID,
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
expect(intent).toEqual({ kind: "button", button: "invite" });
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("ignores a button intent that names no button", () => {
|
|
40
|
+
const intent = readQuickActionIntent(
|
|
41
|
+
{
|
|
42
|
+
[QUICK_ACTION_QUERY_KEY]: QUICK_ACTION_BUTTON,
|
|
43
|
+
[QUICK_ACTION_COMPONENT_KEY]: COMPONENT_ID,
|
|
44
|
+
},
|
|
45
|
+
COMPONENT_ID,
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
expect(intent).toBeUndefined();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("ignores an intent addressed to another table view", () => {
|
|
52
|
+
const intent = readQuickActionIntent(
|
|
53
|
+
{
|
|
54
|
+
[QUICK_ACTION_QUERY_KEY]: QUICK_ACTION_ADD,
|
|
55
|
+
[QUICK_ACTION_COMPONENT_KEY]: "other-table",
|
|
56
|
+
},
|
|
57
|
+
COMPONENT_ID,
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
expect(intent).toBeUndefined();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("ignores an unknown intent", () => {
|
|
64
|
+
const intent = readQuickActionIntent(
|
|
65
|
+
{
|
|
66
|
+
[QUICK_ACTION_QUERY_KEY]: "unknown",
|
|
67
|
+
[QUICK_ACTION_COMPONENT_KEY]: COMPONENT_ID,
|
|
68
|
+
},
|
|
69
|
+
COMPONENT_ID,
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
expect(intent).toBeUndefined();
|
|
73
|
+
});
|
|
74
|
+
});
|