@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.
Files changed (205) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/dist/config.d.ts +1 -0
  3. package/dist/config.js.map +1 -1
  4. package/dist/hooks/invite-extensions.js +2 -2
  5. package/dist/hooks/invite-extensions.js.map +1 -1
  6. package/dist/implementations/dms/layout-banners.d.ts +18 -0
  7. package/dist/implementations/dms/layout-banners.js +60 -0
  8. package/dist/implementations/dms/layout-banners.js.map +1 -0
  9. package/dist/implementations/dms/page.d.ts +3 -0
  10. package/dist/implementations/dms/page.js +90 -28
  11. package/dist/implementations/dms/page.js.map +1 -1
  12. package/dist/implementations/dms/quick-actions.d.ts +5 -0
  13. package/dist/implementations/dms/quick-actions.js.map +1 -1
  14. package/dist/implementations/dms/resolver-timeout.d.ts +1 -0
  15. package/dist/implementations/dms/resolver-timeout.js +13 -0
  16. package/dist/implementations/dms/resolver-timeout.js.map +1 -0
  17. package/dist/implementations/dms/saas-mode.d.ts +8 -0
  18. package/dist/implementations/dms/saas-mode.js +20 -0
  19. package/dist/implementations/dms/saas-mode.js.map +1 -0
  20. package/dist/implementations/dms/stale-session.d.ts +2 -0
  21. package/dist/implementations/dms/stale-session.js +16 -0
  22. package/dist/implementations/dms/stale-session.js.map +1 -0
  23. package/dist/implementations/dms-auth/index.d.ts +3 -1
  24. package/dist/implementations/dms-auth/index.js +38 -8
  25. package/dist/implementations/dms-auth/index.js.map +1 -1
  26. package/dist/implementations/dms-html-render/service-jwt.js +2 -1
  27. package/dist/implementations/dms-html-render/service-jwt.js.map +1 -1
  28. package/dist/index.js +2 -1
  29. package/dist/index.js.map +1 -1
  30. package/dist/pages/settings/users/index.d.ts +1 -0
  31. package/dist/pages/settings/users/index.js +1 -0
  32. package/dist/pages/settings/users/index.js.map +1 -1
  33. package/dist/pages/settings/users/invite-extension-routes.d.ts +4 -0
  34. package/dist/pages/settings/users/invite-extension-routes.js +57 -0
  35. package/dist/pages/settings/users/invite-extension-routes.js.map +1 -0
  36. package/dist/pages/settings/users/invites.d.ts +2 -1
  37. package/dist/pages/settings/users/invites.js +17 -5
  38. package/dist/pages/settings/users/invites.js.map +1 -1
  39. package/dist/pages/settings/users/members.d.ts +4 -1
  40. package/dist/pages/settings/users/members.js +10 -6
  41. package/dist/pages/settings/users/members.js.map +1 -1
  42. package/dist/pages/settings/users/notifications.js +1 -1
  43. package/dist/pages/settings/users/notifications.js.map +1 -1
  44. package/dist/pages/settings/users/quick-actions.d.ts +1 -0
  45. package/dist/pages/settings/users/quick-actions.js +22 -0
  46. package/dist/pages/settings/users/quick-actions.js.map +1 -0
  47. package/dist/routes/auth/client-ip.d.ts +7 -0
  48. package/dist/routes/auth/client-ip.js +42 -0
  49. package/dist/routes/auth/client-ip.js.map +1 -0
  50. package/dist/routes/auth/index.d.ts +1 -0
  51. package/dist/routes/auth/index.js +1 -0
  52. package/dist/routes/auth/index.js.map +1 -1
  53. package/dist/routes/auth/signup.d.ts +2 -0
  54. package/dist/routes/auth/signup.js +7 -2
  55. package/dist/routes/auth/signup.js.map +1 -1
  56. package/dist/routes/auth.d.ts +3 -0
  57. package/dist/routes/auth.js +15 -4
  58. package/dist/routes/auth.js.map +1 -1
  59. package/dist/test/integration/auth/client-ip.test.d.ts +1 -0
  60. package/dist/test/integration/auth/client-ip.test.js +14 -0
  61. package/dist/test/integration/auth/client-ip.test.js.map +1 -0
  62. package/dist/test/integration/invite-extension-edit.test.d.ts +1 -0
  63. package/dist/test/integration/invite-extension-edit.test.js +122 -0
  64. package/dist/test/integration/invite-extension-edit.test.js.map +1 -0
  65. package/dist/test/integration/page-session.test.d.ts +1 -0
  66. package/dist/test/integration/page-session.test.js +52 -0
  67. package/dist/test/integration/page-session.test.js.map +1 -0
  68. package/dist/test/integration/saas-mode-ownership.test.d.ts +1 -0
  69. package/dist/test/integration/saas-mode-ownership.test.js +81 -0
  70. package/dist/test/integration/saas-mode-ownership.test.js.map +1 -0
  71. package/dist/test/integration/tenant-lifecycle.test.js +38 -0
  72. package/dist/test/integration/tenant-lifecycle.test.js.map +1 -1
  73. package/dist/test/unit/client-ip.test.d.ts +1 -0
  74. package/dist/test/unit/client-ip.test.js +77 -0
  75. package/dist/test/unit/client-ip.test.js.map +1 -0
  76. package/dist/test/unit/implementations/dms/layout-banners.test.d.ts +1 -0
  77. package/dist/test/unit/implementations/dms/layout-banners.test.js +217 -0
  78. package/dist/test/unit/implementations/dms/layout-banners.test.js.map +1 -0
  79. package/dist/test/unit/implementations/dms/quick-actions-button.test.d.ts +1 -0
  80. package/dist/test/unit/implementations/dms/quick-actions-button.test.js +220 -0
  81. package/dist/test/unit/implementations/dms/quick-actions-button.test.js.map +1 -0
  82. package/dist/test/unit/implementations/dms-html-render/generate-html.test.js +1 -1
  83. package/dist/test/unit/implementations/dms-html-render/generate-html.test.js.map +1 -1
  84. package/dist/test/unit/implementations/dms-html-render/service-jwt.test.d.ts +1 -0
  85. package/dist/test/unit/implementations/dms-html-render/service-jwt.test.js +37 -0
  86. package/dist/test/unit/implementations/dms-html-render/service-jwt.test.js.map +1 -0
  87. package/dist/test/unit/interfaces/dms/invite-extension.test.js +138 -12
  88. package/dist/test/unit/interfaces/dms/invite-extension.test.js.map +1 -1
  89. package/dist/test/unit/interfaces/dms/saas-mode.test.d.ts +1 -0
  90. package/dist/test/unit/interfaces/dms/saas-mode.test.js +42 -0
  91. package/dist/test/unit/interfaces/dms/saas-mode.test.js.map +1 -0
  92. package/dist/test/unit/interfaces/dms/tenant-invite-email.test.d.ts +1 -0
  93. package/dist/test/unit/interfaces/dms/tenant-invite-email.test.js +53 -0
  94. package/dist/test/unit/interfaces/dms/tenant-invite-email.test.js.map +1 -0
  95. package/dist/test/unit/interfaces/dms-base/table-view-form-slots.test.d.ts +1 -0
  96. package/dist/test/unit/interfaces/dms-base/table-view-form-slots.test.js +80 -0
  97. package/dist/test/unit/interfaces/dms-base/table-view-form-slots.test.js.map +1 -0
  98. package/dist/test/unit/interfaces/dms-base/table-view-tab-count-warning.test.d.ts +1 -0
  99. package/dist/test/unit/interfaces/dms-base/table-view-tab-count-warning.test.js +96 -0
  100. package/dist/test/unit/interfaces/dms-base/table-view-tab-count-warning.test.js.map +1 -0
  101. package/dist/test/unit/pages/settings/invite-member-quick-action.test.d.ts +1 -0
  102. package/dist/test/unit/pages/settings/invite-member-quick-action.test.js +104 -0
  103. package/dist/test/unit/pages/settings/invite-member-quick-action.test.js.map +1 -0
  104. package/dist/test/unit/pages/settings/notifications-access-gate.test.d.ts +1 -0
  105. package/dist/test/unit/pages/settings/notifications-access-gate.test.js +78 -0
  106. package/dist/test/unit/pages/settings/notifications-access-gate.test.js.map +1 -0
  107. package/dist/test/unit/signup-account-guard.test.js +20 -2
  108. package/dist/test/unit/signup-account-guard.test.js.map +1 -1
  109. package/dist/test/unit/utils/admin-invite-email.test.d.ts +1 -0
  110. package/dist/test/unit/utils/admin-invite-email.test.js +69 -0
  111. package/dist/test/unit/utils/admin-invite-email.test.js.map +1 -0
  112. package/dist/utils/admin-invite-email.d.ts +14 -0
  113. package/dist/utils/admin-invite-email.js +51 -0
  114. package/dist/utils/admin-invite-email.js.map +1 -0
  115. package/dist/utils/index.d.ts +0 -1
  116. package/dist/utils/index.js +0 -1
  117. package/dist/utils/index.js.map +1 -1
  118. package/dist/validation/auth.schema.d.ts +3 -3
  119. package/dist/validation/auth.schema.js +1 -1
  120. package/dist/validation/auth.schema.js.map +1 -1
  121. package/frontend-vue/layers/dms-auth/app/custom-pages/auth/signup.vue +37 -8
  122. package/frontend-vue/layers/dms-auth/app/middleware/auth.ts +5 -2
  123. package/frontend-vue/layers/dms-auth/i18n/locales/auth-en-GB.json +3 -1
  124. package/frontend-vue/layers/dms-auth/i18n/locales/auth-fr-FR.json +3 -1
  125. package/frontend-vue/layers/dms-core/app/composables/useApiError.ts +27 -2
  126. package/frontend-vue/layers/dms-core/app/utils/menu.ts +26 -10
  127. package/frontend-vue/layers/dms-core/app/utils/routePath.ts +23 -0
  128. package/frontend-vue/layers/dms-core/i18n/locales/core-en-GB.json +3 -0
  129. package/frontend-vue/layers/dms-core/i18n/locales/core-fr-FR.json +3 -0
  130. package/frontend-vue/layers/dms-core/shared/types/runtime-config.d.ts +4 -1
  131. package/frontend-vue/layers/dms-layout/app/app.config.ts +9 -1
  132. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardBanners.vue +50 -0
  133. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardHeader.vue +16 -13
  134. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardSidebar.vue +2 -2
  135. package/frontend-vue/layers/dms-layout/app/build/components/notification/NotificationPopover.vue +11 -5
  136. package/frontend-vue/layers/dms-layout/app/build/components/notification/widgetRequest.ts +20 -0
  137. package/frontend-vue/layers/dms-layout/app/components/navigation/CollapsedMenuItem.vue +0 -5
  138. package/frontend-vue/layers/dms-layout/app/components/profile/ProfileLanguage.vue +3 -23
  139. package/frontend-vue/layers/dms-layout/app/composables/layout/useLayoutBanners.ts +59 -0
  140. package/frontend-vue/layers/dms-layout/app/composables/page/useQuickActions.ts +15 -2
  141. package/frontend-vue/layers/dms-layout/app/composables/profile/useUserLanguage.ts +33 -0
  142. package/frontend-vue/layers/dms-layout/app/custom-layouts/DefaultLayout.vue +2 -0
  143. package/frontend-vue/layers/dms-layout/app/emails/EmailAdminInvite.vue +54 -9
  144. package/frontend-vue/layers/dms-layout/app/error.vue +17 -2
  145. package/frontend-vue/layers/dms-layout/app/plugins/command-palette-session.client.ts +2 -1
  146. package/frontend-vue/layers/dms-layout/app/types/page.ts +22 -0
  147. package/frontend-vue/layers/dms-layout/app/utils/breadcrumb.ts +48 -0
  148. package/frontend-vue/layers/dms-layout/app/utils/favorites.ts +94 -0
  149. package/frontend-vue/layers/dms-layout/i18n/locales/layout-en-GB.json +21 -2
  150. package/frontend-vue/layers/dms-layout/i18n/locales/layout-fr-FR.json +21 -2
  151. package/frontend-vue/layers/dms-onboarding/app/composables/onboarding/index.ts +1 -1
  152. package/frontend-vue/layers/dms-onboarding/app/composables/onboarding/{useOnboardingMiddleware.ts → useOnboarding.ts} +0 -12
  153. package/frontend-vue/layers/dms-onboarding/app/custom-pages/onboarding.vue +4 -1
  154. package/frontend-vue/layers/dms-onboarding/app/middleware/onboarding.global.ts +21 -4
  155. package/frontend-vue/layers/dms-ui/app/build/components/containers/drawer/DynamicDrawer.vue +13 -3
  156. package/frontend-vue/layers/dms-ui/app/build/components/containers/modal/DynamicModal.vue +13 -3
  157. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerDefaults.ts +31 -3
  158. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationBadges.ts +36 -0
  159. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViewConfig.ts +2 -7
  160. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/columnVisibility.ts +13 -0
  161. package/frontend-vue/layers/dms-ui/app/build/types/tree.ts +5 -10
  162. package/frontend-vue/layers/dms-ui/app/components/PasswordStrength.vue +1 -1
  163. package/frontend-vue/layers/dms-ui/app/components/activity/ActivityItem.vue +1 -1
  164. package/frontend-vue/layers/dms-ui/app/components/banner/Banner.vue +1 -1
  165. package/frontend-vue/layers/dms-ui/app/components/card/NavCard.vue +2 -2
  166. package/frontend-vue/layers/dms-ui/app/components/chart/Chart.vue +6 -16
  167. package/frontend-vue/layers/dms-ui/app/components/chart/ChartCard.vue +2 -2
  168. package/frontend-vue/layers/dms-ui/app/components/chart/internal/TrendBadge.vue +1 -1
  169. package/frontend-vue/layers/dms-ui/app/components/copy/CopyButton.vue +1 -1
  170. package/frontend-vue/layers/dms-ui/app/components/kpi/KpiCard.vue +3 -3
  171. package/frontend-vue/layers/dms-ui/app/components/master-detail/MasterDetail.vue +5 -1
  172. package/frontend-vue/layers/dms-ui/app/components/segmented/Segmented.vue +1 -1
  173. package/frontend-vue/layers/dms-ui/app/components/status/StatusSummary.vue +1 -1
  174. package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +33 -10
  175. package/frontend-vue/layers/dms-ui/app/composables/chart/donutRecords.ts +26 -0
  176. package/frontend-vue/layers/dms-ui/app/composables/tab/types/props.ts +4 -2
  177. package/frontend-vue/layers/dms-ui/app/composables/tab/useTab.ts +3 -3
  178. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/column.ts +1 -1
  179. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/custom-button.ts +2 -0
  180. package/frontend-vue/layers/dms-ui/app/types/quick-actions.ts +38 -3
  181. package/frontend-vue/tests/admin-invite-email.test.ts +86 -0
  182. package/frontend-vue/tests/api-error-title.test.ts +24 -0
  183. package/frontend-vue/tests/auth-middleware-error.test.ts +58 -0
  184. package/frontend-vue/tests/breadcrumb-target.test.ts +50 -0
  185. package/frontend-vue/tests/button-disabled-theme.test.ts +21 -0
  186. package/frontend-vue/tests/chart-nested-donut.test.ts +97 -0
  187. package/frontend-vue/tests/error-page-message.test.ts +83 -0
  188. package/frontend-vue/tests/favorite-page.test.ts +104 -0
  189. package/frontend-vue/tests/icon-aria-hidden.test.ts +29 -0
  190. package/frontend-vue/tests/layout-banners.test.ts +224 -0
  191. package/frontend-vue/tests/menu-navigation.test.ts +39 -10
  192. package/frontend-vue/tests/notification-popover-errors.test.ts +147 -0
  193. package/frontend-vue/tests/onboarding-middleware.test.ts +103 -0
  194. package/frontend-vue/tests/password-strength-meter.test.ts +33 -0
  195. package/frontend-vue/tests/quick-action-intent.test.ts +74 -0
  196. package/frontend-vue/tests/quick-actions.test.ts +23 -0
  197. package/frontend-vue/tests/relation-badges.test.ts +32 -0
  198. package/frontend-vue/tests/signup-invitation-link.test.ts +126 -0
  199. package/frontend-vue/tests/skeleton-paragraph.test.ts +30 -0
  200. package/frontend-vue/tests/tab-without-component-id.test.ts +51 -0
  201. package/frontend-vue/tests/table-column-visibility.test.ts +20 -0
  202. package/frontend-vue/tests/user-language.test.ts +58 -0
  203. package/frontend-vue/tests/vue-inventory.test.ts +1 -1
  204. package/package.json +2 -2
  205. package/skills/dms/REFERENCE.md +1 -1
@@ -2,6 +2,8 @@ import { afterEach, describe, expect, it, vi } from "vitest";
2
2
  import { dispatchQuickActionTarget } from "../layers/dms-layout/app/composables/page/useQuickActions";
3
3
  import {
4
4
  QUICK_ACTION_ADD,
5
+ QUICK_ACTION_BUTTON,
6
+ QUICK_ACTION_BUTTON_KEY,
5
7
  QUICK_ACTION_COMPONENT_KEY,
6
8
  QUICK_ACTION_QUERY_KEY,
7
9
  } from "../layers/dms-ui/app/types/quick-actions";
@@ -29,4 +31,25 @@ describe("dispatchQuickActionTarget", () => {
29
31
  },
30
32
  });
31
33
  });
34
+
35
+ it("routes button actions to the target table with the button to press", () => {
36
+ const navigateDms = vi.fn();
37
+ vi.stubGlobal("navigateDms", navigateDms);
38
+
39
+ dispatchQuickActionTarget({
40
+ type: "button",
41
+ to: "/settings/user/members",
42
+ component: "table",
43
+ button: "invite",
44
+ });
45
+
46
+ expect(navigateDms).toHaveBeenCalledWith({
47
+ path: "/settings/user/members",
48
+ query: {
49
+ [QUICK_ACTION_QUERY_KEY]: QUICK_ACTION_BUTTON,
50
+ [QUICK_ACTION_COMPONENT_KEY]: "table",
51
+ [QUICK_ACTION_BUTTON_KEY]: "invite",
52
+ },
53
+ });
54
+ });
32
55
  });
@@ -0,0 +1,32 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { buildRelationBadges } from "../layers/dms-ui/app/build/composables/data-types/relationBadges";
3
+
4
+ describe("buildRelationBadges", () => {
5
+ it("reads each resolved related row through the label key", () => {
6
+ expect(
7
+ buildRelationBadges([{ name: "Admin" }, { name: "Billing" }], "name"),
8
+ ).to.deep.equal({ visible: ["Admin", "Billing"], hidden: [] });
9
+ });
10
+
11
+ it("folds the labels past the limit into the hidden list", () => {
12
+ const roles = ["A", "B", "C", "D", "E"].map((name) => ({ name }));
13
+ expect(buildRelationBadges(roles, "name", 3)).to.deep.equal({
14
+ visible: ["A", "B", "C"],
15
+ hidden: ["D", "E"],
16
+ });
17
+ });
18
+
19
+ it("shows a raw id when the relation was not resolved", () => {
20
+ expect(buildRelationBadges(["role-1"], "name").visible).to.deep.equal([
21
+ "role-1",
22
+ ]);
23
+ });
24
+
25
+ it("drops items without a label and renders nothing for no value", () => {
26
+ expect(buildRelationBadges([{ other: "x" }, null], "name")).to.deep.equal({
27
+ visible: [],
28
+ hidden: [],
29
+ });
30
+ expect(buildRelationBadges([], "name").visible).to.deep.equal([]);
31
+ });
32
+ });
@@ -0,0 +1,126 @@
1
+ // @vitest-environment jsdom
2
+ import { afterEach, beforeEach, expect, it, vi } from "vitest";
3
+ import {
4
+ computed,
5
+ createApp,
6
+ defineComponent,
7
+ h,
8
+ reactive,
9
+ ref,
10
+ type App,
11
+ } from "vue";
12
+ import * as z from "zod";
13
+ import SignupPage from "../layers/dms-auth/app/custom-pages/auth/signup.vue";
14
+
15
+ const route = reactive<{ query: Record<string, unknown> }>({ query: {} });
16
+ let app: App;
17
+ let host: HTMLDivElement;
18
+ const locale = ref("en");
19
+ const setLocale = vi.fn(async (code: string) => {
20
+ locale.value = code;
21
+ });
22
+
23
+ const Passthrough = defineComponent({
24
+ inheritAttrs: false,
25
+ setup:
26
+ (_, { attrs, slots }) =>
27
+ () =>
28
+ h("div", { "data-testid": attrs["data-testid"] }, slots.default?.()),
29
+ });
30
+
31
+ const LinkButton = defineComponent({
32
+ props: { label: String, to: String },
33
+ setup: (props) => () => h("a", { href: props.to }, props.label),
34
+ });
35
+
36
+ function installRuntime() {
37
+ Object.entries({ computed, ref, reactive }).forEach(([key, value]) =>
38
+ vi.stubGlobal(key, value),
39
+ );
40
+ vi.stubGlobal("useI18n", () => ({
41
+ locale,
42
+ locales: ref([{ code: "en" }, { code: "fr" }]),
43
+ setLocale,
44
+ }));
45
+ vi.stubGlobal("useDmsRuntimeConfig", () => ({
46
+ public: { dms: { mustValidateEmail: false } },
47
+ }));
48
+ vi.stubGlobal("useHomepage", () => "/");
49
+ vi.stubGlobal("useDmsRoute", () => route);
50
+ vi.stubGlobal("passwordSchema", z.string());
51
+ vi.stubGlobal("usePasswordStrength", () => ({
52
+ strength: ref([]),
53
+ score: ref(0),
54
+ color: ref("error"),
55
+ }));
56
+ }
57
+
58
+ function mountSignup(query: Record<string, unknown>) {
59
+ route.query = query;
60
+ app = createApp(SignupPage);
61
+ app.config.globalProperties.$t = (key: string) => key;
62
+ for (const name of [
63
+ "DmsCard",
64
+ "UIcon",
65
+ "UForm",
66
+ "UFormField",
67
+ "UInput",
68
+ "DmsOAuthButtons",
69
+ "DmsPasswordStrength",
70
+ "DmsLink",
71
+ ]) {
72
+ app.component(name, Passthrough);
73
+ }
74
+ app.component("UButton", LinkButton);
75
+ app.mount(host);
76
+ }
77
+
78
+ beforeEach(() => {
79
+ locale.value = "en";
80
+ setLocale.mockClear();
81
+ installRuntime();
82
+ host = document.createElement("div");
83
+ });
84
+ afterEach(() => {
85
+ app?.unmount();
86
+ vi.unstubAllGlobals();
87
+ });
88
+
89
+ it.each([{}, { token: "" }, { token: ["a", "b"] }])(
90
+ "explains an invitation link without its token (%o)",
91
+ (query) => {
92
+ expect(() => mountSignup(query)).not.toThrow();
93
+ expect(
94
+ host.querySelector('[data-testid="signup-invalid-invitation"]'),
95
+ ).not.toBe(null);
96
+ expect(host.textContent).toContain(
97
+ "page.signup.invalid_invitation_description",
98
+ );
99
+ expect(host.querySelector('a[href="/auth"]')?.textContent).toBe(
100
+ "button.login",
101
+ );
102
+ },
103
+ );
104
+
105
+ it("shows the signup form for an invitation link with its token", () => {
106
+ mountSignup({ token: "invite-token", email: "new@local.test" });
107
+ expect(host.querySelector('[data-testid="signup-invalid-invitation"]')).toBe(
108
+ null,
109
+ );
110
+ expect(host.textContent).toContain("page.signup.create_account_title");
111
+ });
112
+
113
+ it("opens in the language the invitation was written in", () => {
114
+ mountSignup({ token: "invite-token", email: "new@local.test", lang: "fr" });
115
+ expect(setLocale).toHaveBeenCalledWith("fr");
116
+ expect(locale.value).toBe("fr");
117
+ });
118
+
119
+ it.each([{}, { lang: "en" }, { lang: "de" }, { lang: ["fr", "en"] }])(
120
+ "keeps the current language for an invitation link carrying %o",
121
+ (query) => {
122
+ mountSignup({ token: "invite-token", email: "new@local.test", ...query });
123
+ expect(setLocale).not.toHaveBeenCalled();
124
+ expect(locale.value).toBe("en");
125
+ },
126
+ );
@@ -0,0 +1,30 @@
1
+ import { readFileSync, readdirSync, statSync } from "node:fs";
2
+ import { join, relative } from "node:path";
3
+ import { describe, expect, it } from "vitest";
4
+
5
+ const PARAGRAPH = /<p\b[^>]*>(.*?)<\/p>/gs;
6
+ const SKELETON = /<USkeleton\b/;
7
+
8
+ function walk(directory: string): string[] {
9
+ return readdirSync(directory).flatMap((name) => {
10
+ const path = join(directory, name);
11
+ return statSync(path).isDirectory() ? walk(path) : [path];
12
+ });
13
+ }
14
+
15
+ describe("skeleton placement", () => {
16
+ // The skeleton renders a <div>. The browser's parser closes an open <p>
17
+ // before a <div>, so the server markup no longer matches the client tree
18
+ // and hydration reports a mismatch.
19
+ it("keeps skeletons out of paragraphs", () => {
20
+ const offenders = walk(join(process.cwd(), "layers"))
21
+ .filter((path) => path.endsWith(".vue"))
22
+ .flatMap((path) =>
23
+ [...readFileSync(path, "utf8").matchAll(PARAGRAPH)]
24
+ .filter(([, content]) => SKELETON.test(content ?? ""))
25
+ .map(() => relative(process.cwd(), path)),
26
+ );
27
+
28
+ expect(offenders).toEqual([]);
29
+ });
30
+ });
@@ -0,0 +1,51 @@
1
+ import { afterEach, beforeEach, expect, it, vi } from "vitest";
2
+ import { computed, nextTick, reactive, ref, watch } from "vue";
3
+ import { useTab } from "../layers/dms-ui/app/composables/tab/useTab";
4
+ import { TabEvents } from "../layers/dms-ui/app/composables/tab/types/events";
5
+ import type { TabProps } from "../layers/dms-ui/app/composables/tab/types";
6
+
7
+ const TAB_COUNT = 3;
8
+ const sendComponentEvent = vi.fn();
9
+
10
+ beforeEach(() => {
11
+ sendComponentEvent.mockClear();
12
+ Object.entries({ computed, ref, watch, TabEvents }).forEach(([key, value]) =>
13
+ vi.stubGlobal(key, value),
14
+ );
15
+ vi.stubGlobal("isNumber", (value: unknown) => typeof value === "number");
16
+ vi.stubGlobal("isString", (value: unknown) => typeof value === "string");
17
+ vi.stubGlobal("useComponentEvent", () => ({ sendComponentEvent }));
18
+ vi.stubGlobal("useWatch", () => ({ isLoading: ref(false), state: ref({}) }));
19
+ vi.stubGlobal("useDmsRoute", () => reactive({ query: {} }));
20
+ vi.stubGlobal("useDmsRouter", () => ({ push: vi.fn() }));
21
+ });
22
+ afterEach(() => {
23
+ vi.unstubAllGlobals();
24
+ });
25
+
26
+ it("switches tabs without announcing them when no component owns the set", async () => {
27
+ const { activeTab, goToTab } = useTab(TAB_COUNT, { items: [] } as TabProps);
28
+
29
+ goToTab(1);
30
+ await nextTick();
31
+
32
+ expect(activeTab.value).toBe("1");
33
+ expect(sendComponentEvent).not.toHaveBeenCalled();
34
+ });
35
+
36
+ it("announces a tab change for a registered component", async () => {
37
+ const { goToTab } = useTab(TAB_COUNT, {
38
+ items: [],
39
+ componentId: "settings.tabs",
40
+ pageId: "settings",
41
+ } as TabProps);
42
+
43
+ goToTab(2);
44
+ await nextTick();
45
+
46
+ expect(sendComponentEvent).toHaveBeenCalledWith(
47
+ TabEvents.TAB_CHANGE,
48
+ "settings.tabs",
49
+ { previousTab: 0, currentTab: 2 },
50
+ );
51
+ });
@@ -0,0 +1,20 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { buildInitialColumnVisibility } from "../layers/dms-ui/app/build/composables/table-view/utils/columnVisibility";
3
+
4
+ describe("buildInitialColumnVisibility", () => {
5
+ it("hides the columns declared isVisible: false", () => {
6
+ expect(
7
+ buildInitialColumnVisibility([
8
+ { id: "number" },
9
+ { id: "amount", isVisible: true },
10
+ { id: "stripeId", isVisible: false },
11
+ ]),
12
+ ).toEqual({ number: true, amount: true, stripeId: false });
13
+ });
14
+
15
+ it("keeps every column visible when none opts out", () => {
16
+ expect(buildInitialColumnVisibility([{ id: "name" }])).toEqual({
17
+ name: true,
18
+ });
19
+ });
20
+ });
@@ -0,0 +1,58 @@
1
+ import { afterEach, describe, expect, it, vi } from "vitest";
2
+ import { useUserLanguage } from "../layers/dms-layout/app/composables/profile/useUserLanguage";
3
+
4
+ interface LanguageHarness {
5
+ setLocale: ReturnType<typeof vi.fn>;
6
+ authFetch: ReturnType<typeof vi.fn>;
7
+ refreshUser: ReturnType<typeof vi.fn>;
8
+ }
9
+
10
+ function stubComposables(authFetch = vi.fn(async () => ({}))): LanguageHarness {
11
+ const harness: LanguageHarness = {
12
+ setLocale: vi.fn(async () => {}),
13
+ authFetch,
14
+ refreshUser: vi.fn(async () => {}),
15
+ };
16
+ vi.stubGlobal("useDmsApp", () => ({
17
+ $i18n: { setLocale: harness.setLocale },
18
+ }));
19
+ vi.stubGlobal("useAuthFetch", () => ({ $authFetch: harness.authFetch }));
20
+ vi.stubGlobal("useUserSession", () => ({
21
+ user: { value: { name: "Ada", email: "ada@example.com", language: "en" } },
22
+ fetch: harness.refreshUser,
23
+ }));
24
+ return harness;
25
+ }
26
+
27
+ afterEach(() => {
28
+ vi.unstubAllGlobals();
29
+ });
30
+
31
+ describe("useUserLanguage", () => {
32
+ it("applies the language and stores it on the user's profile", async () => {
33
+ const harness = stubComposables();
34
+
35
+ await useUserLanguage().changeLanguage("fr");
36
+
37
+ expect(harness.setLocale).toHaveBeenCalledWith("fr");
38
+ expect(harness.authFetch).toHaveBeenCalledWith("/settings/user/profile", {
39
+ method: "POST",
40
+ body: { name: "Ada", email: "ada@example.com", language: "fr" },
41
+ });
42
+ expect(harness.refreshUser).toHaveBeenCalled();
43
+ });
44
+
45
+ it("keeps the applied language when the profile update fails", async () => {
46
+ const harness = stubComposables(
47
+ vi.fn(async () => {
48
+ throw new Error("offline");
49
+ }),
50
+ );
51
+
52
+ await expect(useUserLanguage().changeLanguage("fr")).resolves.toBe(
53
+ undefined,
54
+ );
55
+ expect(harness.setLocale).toHaveBeenCalledWith("fr");
56
+ expect(harness.refreshUser).not.toHaveBeenCalled();
57
+ });
58
+ });
@@ -2,7 +2,7 @@ import { readFileSync, readdirSync, statSync } from "node:fs";
2
2
  import { join, relative } from "node:path";
3
3
  import { describe, expect, it } from "vitest";
4
4
 
5
- const EXPECTED_VUE_FILES = 143;
5
+ const EXPECTED_VUE_FILES = 144;
6
6
 
7
7
  function walk(directory: string): string[] {
8
8
  return readdirSync(directory).flatMap((name) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antelopejs/dms",
3
- "version": "0.4.4",
3
+ "version": "0.4.6",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/AntelopeJS/dms.git"
@@ -36,7 +36,7 @@
36
36
  "@antelopejs/interface-data-api": ">=0.1.9 <1.0.0",
37
37
  "@antelopejs/interface-database": ">=0.1.7 <1.0.0",
38
38
  "@antelopejs/interface-database-decorators": ">=0.1.6 <1.0.0",
39
- "@antelopejs/interface-dms": ">=0.2.0 <1.0.0",
39
+ "@antelopejs/interface-dms": ">=0.2.5 <1.0.0",
40
40
  "@antelopejs/interface-email": ">=0.0.5 <1.0.0",
41
41
  "@antelopejs/interface-file-storage": ">=0.1.3 <1.0.0",
42
42
  "archiver": "^7.0.1",
@@ -21,7 +21,7 @@ List a section to see its files:
21
21
 
22
22
  Everything the DMS exposes to a consumer ships in **one** package, `@antelopejs/interface-dms`,
23
23
  split into subpaths: `/page`, `/component`, `/permissions`, `/guards`, `/db`, `/hooks`, `/realtime`,
24
- `/quick-actions`, `/auth` (+ `/auth/db`), `/base` (+ `/base/form`, `/base/table-view`,
24
+ `/quick-actions`, `/layout-banners`, `/auth` (+ `/auth/db`), `/base` (+ `/base/form`, `/base/table-view`,
25
25
  `/base/data-types`, `/base/types`, …), `/notifications`, `/html-render`. There is no separate
26
26
  `dms-auth` or `dms-base` interface, and nothing is imported through the runtime package
27
27
  `@antelopejs/dms`. Other modules follow the same shape: one `interface-<name>` package per module.