@antelopejs/dms 0.7.21 → 0.8.1
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 +55 -0
- package/dist/implementations/dms/resolver-timeout.d.ts +1 -0
- package/dist/implementations/dms/resolver-timeout.js +13 -3
- package/dist/implementations/dms/resolver-timeout.js.map +1 -1
- package/dist/implementations/dms-auth/account-emails.d.ts +4 -0
- package/dist/implementations/dms-auth/account-emails.js +62 -0
- package/dist/implementations/dms-auth/account-emails.js.map +1 -0
- package/dist/implementations/dms-auth/index.d.ts +1 -3
- package/dist/implementations/dms-auth/index.js +15 -61
- package/dist/implementations/dms-auth/index.js.map +1 -1
- package/dist/pages/settings/users/email-change.js +3 -4
- package/dist/pages/settings/users/email-change.js.map +1 -1
- package/dist/pages/settings/users/invites.js +1 -1
- package/dist/pages/settings/users/invites.js.map +1 -1
- package/dist/pages/settings/users/members.js +1 -1
- package/dist/pages/settings/users/members.js.map +1 -1
- package/dist/pages/settings/users/notifications.js +1 -0
- package/dist/pages/settings/users/notifications.js.map +1 -1
- package/dist/test/antelope.test.js +2 -1
- package/dist/test/antelope.test.js.map +1 -1
- package/dist/test/helpers/db.js +19 -11
- package/dist/test/helpers/db.js.map +1 -1
- package/dist/test/integration/auth/two-factor-codes.test.js +4 -2
- package/dist/test/integration/auth/two-factor-codes.test.js.map +1 -1
- package/dist/test/unit/implementations/dms/dynamic-menu-hardening.test.js +8 -5
- package/dist/test/unit/implementations/dms/dynamic-menu-hardening.test.js.map +1 -1
- package/dist/test/unit/implementations/dms/layout-banners.test.js +20 -7
- package/dist/test/unit/implementations/dms/layout-banners.test.js.map +1 -1
- package/dist/test/unit/implementations/dms-html-render/built-in-emails.test.js +34 -0
- package/dist/test/unit/implementations/dms-html-render/built-in-emails.test.js.map +1 -0
- package/dist/test/unit/interfaces/dms/hook-handler-results.test.js +49 -0
- package/dist/test/unit/interfaces/dms/hook-handler-results.test.js.map +1 -0
- package/dist/test/unit/interfaces/dms/page-topics.test.js +17 -0
- package/dist/test/unit/interfaces/dms/page-topics.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms-base/confirm-dialog-secondary-action.test.js +2 -2
- package/dist/test/unit/interfaces/dms-base/confirm-dialog-secondary-action.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms-base/query-response-delta.test.js +29 -0
- package/dist/test/unit/interfaces/dms-base/query-response-delta.test.js.map +1 -0
- package/dist/test/unit/interfaces/dms-base/resource-form.test.js +5 -0
- package/dist/test/unit/interfaces/dms-base/resource-form.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms-base/table-view-form-redirect.test.js +12 -2
- package/dist/test/unit/interfaces/dms-base/table-view-form-redirect.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms-base/table-view-reduced-chrome.test.js +29 -4
- package/dist/test/unit/interfaces/dms-base/table-view-reduced-chrome.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms-base/table-view-source-reorder.test.js +14 -11
- package/dist/test/unit/interfaces/dms-base/table-view-source-reorder.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms-base/table-view-views.test.js +8 -5
- package/dist/test/unit/interfaces/dms-base/table-view-views.test.js.map +1 -1
- package/dist/test/unit/utils/email-subjects.test.js +27 -0
- package/dist/test/unit/utils/email-subjects.test.js.map +1 -0
- package/dist/utils/admin-invite-email.js +2 -4
- package/dist/utils/admin-invite-email.js.map +1 -1
- package/dist/utils/email-change-mail.d.ts +1 -1
- package/dist/utils/email-change-mail.js +11 -6
- package/dist/utils/email-change-mail.js.map +1 -1
- package/dist/utils/email-subjects.d.ts +3 -0
- package/dist/utils/email-subjects.js +32 -0
- package/dist/utils/email-subjects.js.map +1 -0
- package/dist/utils/export-jobs.js +15 -10
- package/dist/utils/export-jobs.js.map +1 -1
- package/frontend-vue/layers/dms-core/app/utils/url-interpolation.ts +29 -11
- package/frontend-vue/layers/dms-core/shared/types/app-config.ts +17 -0
- package/frontend-vue/layers/dms-layout/app/app.config.ts +5 -0
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardHeader.vue +18 -2
- package/frontend-vue/layers/dms-layout/app/build/components/layout/Footer.vue +16 -1
- package/frontend-vue/layers/dms-layout/app/build/composables/layout/useHeaderActionControls.ts +14 -0
- package/frontend-vue/layers/dms-layout/app/components/layout/HStack.vue +16 -0
- package/frontend-vue/layers/dms-layout/app/components/layout/VStack.vue +2 -0
- package/frontend-vue/layers/dms-layout/app/composables/useFooterLinks.ts +11 -1
- package/frontend-vue/layers/dms-layout/app/custom-pages/settings/roles.vue +1 -1
- package/frontend-vue/layers/dms-layout/app/emails/EmailAdminInvite.vue +1 -1
- package/frontend-vue/layers/dms-layout/app/emails/EmailChangeNotice.vue +13 -7
- package/frontend-vue/layers/dms-layout/app/emails/EmailChangeVerification.vue +18 -7
- package/frontend-vue/layers/dms-layout/app/emails/EmailExportReady.vue +23 -12
- package/frontend-vue/layers/dms-layout/app/emails/EmailResetPassword.vue +18 -7
- package/frontend-vue/layers/dms-layout/app/emails/EmailTwoFactor.vue +23 -4
- package/frontend-vue/layers/dms-layout/app/emails/EmailUserValidation.vue +18 -6
- package/frontend-vue/layers/dms-layout/app/emails/components/EmailOTP.vue +8 -2
- package/frontend-vue/layers/dms-layout/i18n/locales/layout-en-GB.json +40 -0
- package/frontend-vue/layers/dms-layout/i18n/locales/layout-fr-FR.json +40 -0
- package/frontend-vue/layers/dms-ui/app/build/components/form/FormFieldControl.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/build/components/form/FormLoadError.vue +11 -3
- package/frontend-vue/layers/dms-ui/app/build/composables/actions/useActionTargets.ts +27 -36
- package/frontend-vue/layers/dms-ui/app/build/composables/chart/apexTheme.ts +59 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/confirm/confirmDialogTexts.ts +27 -4
- package/frontend-vue/layers/dms-ui/app/build/composables/confirm/useActionConfirm.ts +10 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerDefaults.ts +0 -5
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formEntryOrder.ts +23 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formFooter.ts +19 -15
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formRecordLoad.ts +26 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/page/pageUrlValues.ts +41 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/registerDefaultFunctions.ts +2 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableRows.ts +3 -5
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViewConfig.ts +7 -12
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViewRowActions.ts +3 -3
- package/frontend-vue/layers/dms-ui/app/build/utils/link.ts +7 -3
- package/frontend-vue/layers/dms-ui/app/build/utils/urlVariables.ts +32 -9
- package/frontend-vue/layers/dms-ui/app/components/activity-feed/ActivityFeed.vue +8 -1
- package/frontend-vue/layers/dms-ui/app/components/banner/Banner.vue +9 -4
- package/frontend-vue/layers/dms-ui/app/components/blocks/BannerBlock.vue +5 -11
- package/frontend-vue/layers/dms-ui/app/components/blocks/NavCardGridBlock.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/components/chart/ApexChartHost.vue +32 -1
- package/frontend-vue/layers/dms-ui/app/components/check-list/CheckList.vue +1 -1
- package/frontend-vue/layers/dms-ui/app/components/form/Form.vue +12 -2
- package/frontend-vue/layers/dms-ui/app/components/form/components/Cascader.vue +7 -3
- package/frontend-vue/layers/dms-ui/app/components/form/components/File.vue +7 -0
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputAddress.vue +4 -1
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputCode.vue +4 -2
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputSecret.vue +3 -2
- package/frontend-vue/layers/dms-ui/app/components/form/components/Relation.vue +4 -1
- package/frontend-vue/layers/dms-ui/app/components/grid/GridRow.vue +14 -6
- package/frontend-vue/layers/dms-ui/app/components/grid/columns.ts +52 -3
- package/frontend-vue/layers/dms-ui/app/components/key-value-list/KeyValueList.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/components/list-row/ListRow.vue +9 -2
- package/frontend-vue/layers/dms-ui/app/components/save-bar/InstantSaveBadge.vue +13 -5
- package/frontend-vue/layers/dms-ui/app/components/save-bar/SaveBar.vue +1 -1
- package/frontend-vue/layers/dms-ui/app/components/stat-group/StatGroup.vue +3 -2
- package/frontend-vue/layers/dms-ui/app/components/table-view/CascaderPath.vue +53 -31
- package/frontend-vue/layers/dms-ui/app/composables/chart/apexPlotOptions.ts +15 -4
- package/frontend-vue/layers/dms-ui/app/composables/chart/apexYRange.ts +123 -0
- package/frontend-vue/layers/dms-ui/app/composables/chart/useApexChart.ts +49 -49
- package/frontend-vue/layers/dms-ui/app/composables/chart/useChartTheme.ts +20 -1
- package/frontend-vue/layers/dms-ui/app/composables/confirm/types.ts +1 -1
- package/frontend-vue/layers/dms-ui/app/composables/form/types/props.ts +2 -0
- package/frontend-vue/layers/dms-ui/app/composables/form/useForm.ts +8 -6
- package/frontend-vue/layers/dms-ui/app/composables/table-view/types/action-target.ts +1 -1
- package/frontend-vue/layers/dms-ui/app/composables/table-view/types/config.ts +3 -5
- package/frontend-vue/layers/dms-ui/app/utils/bannerToneIcons.ts +13 -0
- package/frontend-vue/layers/dms-ui/app/utils/cascader.ts +26 -0
- package/frontend-vue/layers/dms-ui/i18n/locales/ui-en-GB.json +4 -0
- package/frontend-vue/layers/dms-ui/i18n/locales/ui-fr-FR.json +4 -0
- package/frontend-vue/package.json +1 -1
- package/frontend-vue/pnpm-lock.yaml +5 -5
- package/frontend-vue/tests/activity-feed-fill-height.test.ts +7 -0
- package/frontend-vue/tests/banner-tone-icon.test.ts +55 -0
- package/frontend-vue/tests/builtin-emails.test.ts +141 -0
- package/frontend-vue/tests/cascader.test.ts +27 -0
- package/frontend-vue/tests/chart-auto-y-range.test.ts +25 -9
- package/frontend-vue/tests/chart-colors.test.ts +8 -0
- package/frontend-vue/tests/chart-resize-after-animation.test.ts +100 -0
- package/frontend-vue/tests/chart-type-defaults.test.ts +128 -0
- package/frontend-vue/tests/check-list-marker.test.ts +58 -0
- package/frontend-vue/tests/confirm-from.test.ts +1 -1
- package/frontend-vue/tests/confirm-secondary-action.test.ts +2 -2
- package/frontend-vue/tests/context-links.test.ts +2 -2
- package/frontend-vue/tests/custom-action-confirm-from.test.ts +2 -2
- package/frontend-vue/tests/download-action-target.test.ts +2 -2
- package/frontend-vue/tests/file-drop-prompt.test.ts +72 -0
- package/frontend-vue/tests/footer-config.test.ts +135 -0
- package/frontend-vue/tests/form-actions.test.ts +7 -0
- package/frontend-vue/tests/form-enclosing-form.test.ts +0 -21
- package/frontend-vue/tests/form-entry-order.test.ts +45 -0
- package/frontend-vue/tests/form-footer-kind.test.ts +11 -6
- package/frontend-vue/tests/form-load-error.test.ts +38 -23
- package/frontend-vue/tests/grid-responsive-columns.test.ts +39 -31
- package/frontend-vue/tests/header-actions.test.ts +55 -9
- package/frontend-vue/tests/instant-save-badge.test.ts +11 -4
- package/frontend-vue/tests/list-row-truncate.test.ts +46 -0
- package/frontend-vue/tests/nuxt-ui-aria-labels.test.ts +1 -28
- package/frontend-vue/tests/period-live-pause.test.ts +0 -13
- package/frontend-vue/tests/period-max-days.test.ts +0 -14
- package/frontend-vue/tests/roles-name-errors.test.ts +2 -3
- package/frontend-vue/tests/row-side-panel.test.ts +0 -12
- package/frontend-vue/tests/save-bar.test.ts +15 -3
- package/frontend-vue/tests/save-status-error.test.ts +2 -2
- package/frontend-vue/tests/table-cell-wrap.test.ts +0 -12
- package/frontend-vue/tests/table-density-menu.test.ts +0 -21
- package/frontend-vue/tests/table-live-hold.test.ts +0 -44
- package/frontend-vue/tests/table-row-action-errors.test.ts +5 -5
- package/frontend-vue/tests/table-row-hover.test.ts +2 -12
- package/frontend-vue/tests/table-scroll-area.test.ts +0 -8
- package/frontend-vue/tests/table-view-card-props.test.ts +0 -17
- package/frontend-vue/tests/table-view-cards-reorder.test.ts +0 -16
- package/frontend-vue/tests/table-view-grouped.test.ts +0 -55
- package/frontend-vue/tests/table-view-pinned-rows.test.ts +0 -15
- package/frontend-vue/tests/table-view-record-link.test.ts +0 -17
- package/frontend-vue/tests/table-view-refresh.test.ts +0 -17
- package/frontend-vue/tests/table-view-sources.test.ts +1 -1
- package/frontend-vue/tests/table-view-url-tab.test.ts +0 -14
- package/frontend-vue/tests/template-tokens.test.ts +235 -0
- package/frontend-vue/tests/tone.test.ts +0 -19
- package/frontend-vue/tests/url-interpolation.test.ts +6 -6
- package/package.json +6 -6
- package/skills/dms/SKILL.md +2 -1
- package/skills/dms-pages/REFERENCE.md +2 -2
- package/skills/dms-project/SKILL.md +0 -1
- package/dist/test/integration/auth/login.test.js +0 -57
- package/dist/test/integration/auth/login.test.js.map +0 -1
- package/dist/test/integration/auth/refresh.test.js +0 -59
- package/dist/test/integration/auth/refresh.test.js.map +0 -1
- package/dist/test/integration/auth/sessions.test.js +0 -58
- package/dist/test/integration/auth/sessions.test.js.map +0 -1
- package/dist/test/integration/auth/signup.test.js +0 -99
- package/dist/test/integration/auth/signup.test.js.map +0 -1
- package/dist/test/unit/skills/dms-dev-script.test.d.ts +0 -1
- package/dist/test/unit/skills/dms-dev-script.test.js +0 -137
- package/dist/test/unit/skills/dms-dev-script.test.js.map +0 -1
- package/frontend-vue/tests/form-reload-on-reopen.test.ts +0 -20
- package/frontend-vue/tests/icon-aria-hidden.test.ts +0 -29
- package/frontend-vue/tests/skeleton-paragraph.test.ts +0 -30
- package/frontend-vue/tests/tab-slot-spacing.test.ts +0 -29
- package/frontend-vue/tests/table-empty-body-viewport.test.ts +0 -25
- package/frontend-vue/tests/table-header-placement.test.ts +0 -21
- package/frontend-vue/tests/vue-inventory.test.ts +0 -107
- /package/dist/test/{integration/auth/login.test.d.ts → unit/implementations/dms-html-render/built-in-emails.test.d.ts} +0 -0
- /package/dist/test/{integration/auth/sessions.test.d.ts → unit/interfaces/dms/hook-handler-results.test.d.ts} +0 -0
- /package/dist/test/{integration/auth/refresh.test.d.ts → unit/interfaces/dms-base/query-response-delta.test.d.ts} +0 -0
- /package/dist/test/{integration/auth/signup.test.d.ts → unit/utils/email-subjects.test.d.ts} +0 -0
|
@@ -5,8 +5,6 @@
|
|
|
5
5
|
* leaves with its component, and focusing a field by id. A page opened on a
|
|
6
6
|
* field's anchor focuses that field.
|
|
7
7
|
*/
|
|
8
|
-
import { readFileSync } from "node:fs";
|
|
9
|
-
import { resolve } from "node:path";
|
|
10
8
|
import { createApp, defineComponent, h } from "vue";
|
|
11
9
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
12
10
|
import {
|
|
@@ -120,22 +118,3 @@ describe("a field's anchor", () => {
|
|
|
120
118
|
expect(linkedFieldId("#%E0%A4%A", FIELD_IDS)).toBe(undefined);
|
|
121
119
|
});
|
|
122
120
|
});
|
|
123
|
-
|
|
124
|
-
describe("the Form", () => {
|
|
125
|
-
const formSource = readFileSync(
|
|
126
|
-
resolve(__dirname, "../layers/dms-ui/app/components/form/Form.vue"),
|
|
127
|
-
"utf8",
|
|
128
|
-
);
|
|
129
|
-
|
|
130
|
-
it("offers its fields the submit, its validators and the focus", () => {
|
|
131
|
-
expect(formSource).toMatch(
|
|
132
|
-
/provide\(FORM_HANDLE_KEY, \{\s*submit: async \(\) => \{\s*await form\.value\?\.submit\(\);\s*\},\s*registerValidator,\s*revealField,\s*\}\);/,
|
|
133
|
-
);
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
it("follows the anchor of a field once mounted, a page form only", () => {
|
|
137
|
-
expect(formSource).toMatch(
|
|
138
|
-
/if \(!inFormContainer && !container\) \{\s*const followFieldAnchor = \(\) => \{\s*const fieldId = linkedFieldId\(window\.location\.hash, allFieldIds\.value\);/,
|
|
139
|
-
);
|
|
140
|
-
});
|
|
141
|
-
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { orderFormEntries } from "../layers/dms-ui/app/build/composables/form/formEntryOrder";
|
|
3
|
+
import type { FormFieldOrGroup } from "../layers/dms-ui/app/composables/form/types/field";
|
|
4
|
+
|
|
5
|
+
const field = (id: string) => ({
|
|
6
|
+
id,
|
|
7
|
+
component: { componentName: "dms-input-text" },
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
const group = (id: string, order?: number) => ({
|
|
11
|
+
id,
|
|
12
|
+
order,
|
|
13
|
+
fields: [field(`${id}Member`)],
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const ids = (entries: FormFieldOrGroup[]) => entries.map((entry) => entry.id);
|
|
17
|
+
|
|
18
|
+
describe("orderFormEntries", () => {
|
|
19
|
+
it("moves the groups giving an order into their places, lowest first", () => {
|
|
20
|
+
const entries: FormFieldOrGroup[] = [
|
|
21
|
+
field("username"),
|
|
22
|
+
group("contact"),
|
|
23
|
+
field("bio"),
|
|
24
|
+
group("social", 2),
|
|
25
|
+
group("address", 1),
|
|
26
|
+
];
|
|
27
|
+
expect(ids(orderFormEntries(entries))).toEqual([
|
|
28
|
+
"username",
|
|
29
|
+
"contact",
|
|
30
|
+
"bio",
|
|
31
|
+
"address",
|
|
32
|
+
"social",
|
|
33
|
+
]);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("keeps the declaration order of equal orders and of no order at all", () => {
|
|
37
|
+
const entries: FormFieldOrGroup[] = [
|
|
38
|
+
group("first", 1),
|
|
39
|
+
field("name"),
|
|
40
|
+
group("second", 1),
|
|
41
|
+
group("loose"),
|
|
42
|
+
];
|
|
43
|
+
expect(ids(orderFormEntries(entries))).toEqual(ids(entries));
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -69,23 +69,28 @@ describe("saveBarState of an action form", () => {
|
|
|
69
69
|
});
|
|
70
70
|
});
|
|
71
71
|
|
|
72
|
-
it("shows
|
|
73
|
-
|
|
72
|
+
it("shows its submit alone, ready, on a page while untouched", () => {
|
|
73
|
+
// A pre-filled form sends its defaults as they are.
|
|
74
|
+
expect(action({})).toEqual({
|
|
75
|
+
showsStatus: false,
|
|
76
|
+
showsSubmit: true,
|
|
77
|
+
isSubmitHeld: false,
|
|
78
|
+
isHidden: false,
|
|
79
|
+
});
|
|
74
80
|
});
|
|
75
81
|
|
|
76
|
-
it("offers Cancel beside its
|
|
77
|
-
// The invite modal: empty, it still shows the way out and its "Invite".
|
|
82
|
+
it("offers Cancel beside its ready submit in a drawer or a modal", () => {
|
|
78
83
|
expect(action({ cancellable: true })).toEqual({
|
|
79
84
|
showsStatus: false,
|
|
80
85
|
secondary: "cancel",
|
|
81
86
|
showsSubmit: true,
|
|
82
|
-
isSubmitHeld:
|
|
87
|
+
isSubmitHeld: false,
|
|
83
88
|
isHidden: false,
|
|
84
89
|
});
|
|
85
90
|
});
|
|
86
91
|
|
|
87
92
|
it("shows its submit alone, ready, when nothing in it can be changed", () => {
|
|
88
|
-
const bar = action({ resettable: false });
|
|
93
|
+
const bar = action({ resettable: false, dirty: true });
|
|
89
94
|
expect(bar.secondary).toBeUndefined();
|
|
90
95
|
expect(bar).toMatchObject({
|
|
91
96
|
showsSubmit: true,
|
|
@@ -4,7 +4,9 @@ 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";
|
|
6
6
|
import {
|
|
7
|
+
type FormLoadFailure,
|
|
7
8
|
type FormLoadSource,
|
|
9
|
+
missingRecordFailure,
|
|
8
10
|
trackFormRecordLoad,
|
|
9
11
|
} from "../layers/dms-ui/app/build/composables/form/formRecordLoad";
|
|
10
12
|
|
|
@@ -72,6 +74,32 @@ describe("a form's record load", () => {
|
|
|
72
74
|
});
|
|
73
75
|
});
|
|
74
76
|
|
|
77
|
+
describe("a form about a record its address names none of", () => {
|
|
78
|
+
const FETCH_URL = "/api/tasks/get?id={{query.id}}";
|
|
79
|
+
|
|
80
|
+
it("reports the record missing while the id is not in the address", () => {
|
|
81
|
+
expect(
|
|
82
|
+
missingRecordFailure({ requiresRecord: true, fetchUrl: FETCH_URL }),
|
|
83
|
+
).toBe("missing");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("reports nothing once the address names the record", () => {
|
|
87
|
+
expect(
|
|
88
|
+
missingRecordFailure({
|
|
89
|
+
requiresRecord: true,
|
|
90
|
+
fetchUrl: FETCH_URL,
|
|
91
|
+
resolvedFetchUrl: "/api/tasks/get?id=1",
|
|
92
|
+
}),
|
|
93
|
+
).toBe(null);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("leaves a form that may open without a record to its defaults", () => {
|
|
97
|
+
// A create form seeded by `?duplicate=` when there is one.
|
|
98
|
+
expect(missingRecordFailure({ fetchUrl: FETCH_URL })).toBe(null);
|
|
99
|
+
expect(missingRecordFailure({ requiresRecord: true })).toBe(null);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
75
103
|
describe("the load error a form shows", () => {
|
|
76
104
|
const Slot = defineComponent({
|
|
77
105
|
props: {
|
|
@@ -91,7 +119,7 @@ describe("the load error a form shows", () => {
|
|
|
91
119
|
]),
|
|
92
120
|
});
|
|
93
121
|
|
|
94
|
-
async function render(failure:
|
|
122
|
+
async function render(failure: FormLoadFailure): Promise<string> {
|
|
95
123
|
const app = createSSRApp({
|
|
96
124
|
render: () => h(FormLoadError, { failure }),
|
|
97
125
|
});
|
|
@@ -118,6 +146,13 @@ describe("the load error a form shows", () => {
|
|
|
118
146
|
expect(html).toContain("dms.form.load_error.retry");
|
|
119
147
|
});
|
|
120
148
|
|
|
149
|
+
it("says no entry is selected, with nothing to retry", async () => {
|
|
150
|
+
const html = await render("missing");
|
|
151
|
+
expect(html).toContain("dms.form.load_error.missing_title");
|
|
152
|
+
expect(html).toContain("dms.form.load_error.missing_description");
|
|
153
|
+
expect(html).not.toContain("dms.form.load_error.retry");
|
|
154
|
+
});
|
|
155
|
+
|
|
121
156
|
it("has its texts in English and French", () => {
|
|
122
157
|
for (const file of ["ui-en-GB.json", "ui-fr-FR.json"]) {
|
|
123
158
|
const locale = JSON.parse(
|
|
@@ -131,30 +166,10 @@ describe("the load error a form shows", () => {
|
|
|
131
166
|
"error_title",
|
|
132
167
|
"forbidden_description",
|
|
133
168
|
"forbidden_title",
|
|
169
|
+
"missing_description",
|
|
170
|
+
"missing_title",
|
|
134
171
|
"retry",
|
|
135
172
|
]);
|
|
136
173
|
}
|
|
137
174
|
});
|
|
138
175
|
});
|
|
139
|
-
|
|
140
|
-
describe("a form whose record did not load", () => {
|
|
141
|
-
const formSource = readFileSync(
|
|
142
|
-
new URL("../layers/dms-ui/app/components/form/Form.vue", import.meta.url),
|
|
143
|
-
"utf8",
|
|
144
|
-
);
|
|
145
|
-
|
|
146
|
-
it("shows the load error instead of its fields", () => {
|
|
147
|
-
expect(formSource).toMatch(
|
|
148
|
-
/<div v-if="loadFailure"[^>]*>\s*<DmsFormLoadError[\s\S]*?<\/div>\s*<div v-else :class="surfaceClasses\.body">/,
|
|
149
|
-
);
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
it("cannot be saved: no save bar, and a submit is refused", () => {
|
|
153
|
-
expect(formSource).toMatch(
|
|
154
|
-
/<DmsSaveBar\s+v-if="canSave && !isInstant && !loadFailure"/,
|
|
155
|
-
);
|
|
156
|
-
expect(formSource).toMatch(
|
|
157
|
-
/function onSubmit\([^)]*\) \{\s*if \(loadFailure\.value \|\| !runValidators\(\)\) return;/,
|
|
158
|
-
);
|
|
159
|
-
});
|
|
160
|
-
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
1
|
import { describe, expect, it } from "vitest";
|
|
3
2
|
import {
|
|
4
3
|
GRID_TRACKS_VAR,
|
|
4
|
+
gridCollapsedCellStyles,
|
|
5
5
|
gridColumnsTemplate,
|
|
6
6
|
gridResponsiveStyles,
|
|
7
7
|
gridScopeId,
|
|
@@ -10,12 +10,6 @@ import {
|
|
|
10
10
|
const GAP = "1rem";
|
|
11
11
|
const MIN = "240px";
|
|
12
12
|
|
|
13
|
-
const sourceOf = (file: string) =>
|
|
14
|
-
readFileSync(
|
|
15
|
-
new URL(`../layers/dms-ui/app/components/grid/${file}`, import.meta.url),
|
|
16
|
-
"utf8",
|
|
17
|
-
);
|
|
18
|
-
|
|
19
13
|
// Guards the exact-fit case: at the width where the track floor is precisely
|
|
20
14
|
// one Nth of the container, binary floats land a hair under the integer.
|
|
21
15
|
const FIT_EPSILON = 1e-6;
|
|
@@ -87,6 +81,15 @@ describe("grid column template", () => {
|
|
|
87
81
|
expect(gridColumnsTemplate(1, GAP, MIN)).toContain("(100% - 0 * 1rem)");
|
|
88
82
|
});
|
|
89
83
|
|
|
84
|
+
it("reads a unitless gap or minimum as pixels, which calc() requires", () => {
|
|
85
|
+
const template = gridColumnsTemplate(2, "0", "160");
|
|
86
|
+
expect(template).toContain("(100% - 1 * 0px)");
|
|
87
|
+
expect(template).toContain("max(160px,");
|
|
88
|
+
expect(resolveColumnCount(template, 553, 0)).toBe(2);
|
|
89
|
+
const css = gridResponsiveStyles(gridScopeId("nested"), 2, "0", MIN);
|
|
90
|
+
expect(css).toContain(`calc(2 * ${MIN} + 1 * 0px)`);
|
|
91
|
+
});
|
|
92
|
+
|
|
90
93
|
it("honours a caller-supplied minimum", () => {
|
|
91
94
|
expect(gridColumnsTemplate(4, GAP, "120px")).toContain("120px");
|
|
92
95
|
expect(
|
|
@@ -95,20 +98,6 @@ describe("grid column template", () => {
|
|
|
95
98
|
});
|
|
96
99
|
});
|
|
97
100
|
|
|
98
|
-
describe("grid components", () => {
|
|
99
|
-
it("drive both the container and its rows from the shared template", () => {
|
|
100
|
-
for (const file of ["Grid.vue", "GridRow.vue"]) {
|
|
101
|
-
const source = sourceOf(file);
|
|
102
|
-
expect(source).toContain("gridColumnsTemplate(");
|
|
103
|
-
expect(source).not.toMatch(/repeat\(\$\{/);
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it("keeps rows spanning the full grid width", () => {
|
|
108
|
-
expect(sourceOf("GridRow.vue")).toContain('gridColumn: "1 / -1"');
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
|
|
112
101
|
describe("grid responsive styles", () => {
|
|
113
102
|
const scope = gridScopeId("grid-child-row1");
|
|
114
103
|
|
|
@@ -158,15 +147,34 @@ describe("grid responsive styles", () => {
|
|
|
158
147
|
});
|
|
159
148
|
});
|
|
160
149
|
|
|
161
|
-
describe("grid
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
150
|
+
describe("grid collapsed cell styles", () => {
|
|
151
|
+
const scope = gridScopeId("grid-child-row2");
|
|
152
|
+
const cells = `[data-dms-grid="${scope}"] > `;
|
|
153
|
+
|
|
154
|
+
it("takes no track for a cell its block leaves empty, but keeps spacers", () => {
|
|
155
|
+
const [hide] = gridCollapsedCellStyles(scope, 2).split("\n");
|
|
156
|
+
expect(hide).toBe(`${cells}:empty:not(.dms-spacer) { display: none; }`);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("widens the last filled cell over each run of trailing empty cells", () => {
|
|
160
|
+
const rules = gridCollapsedCellStyles(scope, 3).split("\n");
|
|
161
|
+
expect(rules).toHaveLength(3);
|
|
162
|
+
expect(rules[1]).toContain(
|
|
163
|
+
":has(+ :empty:not(.dms-spacer) + style:last-child)",
|
|
164
|
+
);
|
|
165
|
+
expect(rules[1]).toContain(
|
|
166
|
+
`grid-column: span min(2, var(${GRID_TRACKS_VAR}, 2));`,
|
|
167
|
+
);
|
|
168
|
+
expect(rules[2]).toContain(
|
|
169
|
+
":has(+ :empty:not(.dms-spacer) + :empty:not(.dms-spacer) + style:last-child)",
|
|
170
|
+
);
|
|
171
|
+
expect(rules[2]).toContain(
|
|
172
|
+
`grid-column: span min(3, var(${GRID_TRACKS_VAR}, 3));`,
|
|
173
|
+
);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("gives a single-column row no rules", () => {
|
|
177
|
+
expect(gridCollapsedCellStyles(scope, 1)).toBe("");
|
|
178
|
+
expect(gridCollapsedCellStyles(scope, 0)).toBe("");
|
|
171
179
|
});
|
|
172
180
|
});
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
computed,
|
|
4
4
|
createApp,
|
|
5
|
+
createSSRApp,
|
|
5
6
|
defineComponent,
|
|
6
7
|
h,
|
|
7
8
|
nextTick,
|
|
@@ -10,6 +11,7 @@ import {
|
|
|
10
11
|
type App,
|
|
11
12
|
type Ref,
|
|
12
13
|
} from "vue";
|
|
14
|
+
import { renderToString } from "vue/server-renderer";
|
|
13
15
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
14
16
|
import DashboardHeader from "../layers/dms-layout/app/build/components/layout/DashboardHeader.vue";
|
|
15
17
|
import {
|
|
@@ -97,18 +99,44 @@ function installRuntime(): void {
|
|
|
97
99
|
);
|
|
98
100
|
}
|
|
99
101
|
|
|
100
|
-
function
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
app = createApp(DashboardHeader);
|
|
104
|
-
app.component("UButton", ButtonStub);
|
|
102
|
+
function createHeaderApp(create: typeof createApp): App {
|
|
103
|
+
const header = create(DashboardHeader);
|
|
104
|
+
header.component("UButton", ButtonStub);
|
|
105
105
|
["UDashboardNavbar", "UTooltip", "UBreadcrumb", "UDropdownMenu"].forEach(
|
|
106
|
-
(name) =>
|
|
106
|
+
(name) => header.component(name, SlotsStub),
|
|
107
107
|
);
|
|
108
|
+
return header;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function mountHeader(): Promise<HTMLElement> {
|
|
112
|
+
const host = document.createElement("div");
|
|
113
|
+
document.body.append(host);
|
|
114
|
+
app = createHeaderApp(createApp);
|
|
115
|
+
app.mount(host);
|
|
116
|
+
await nextTick();
|
|
117
|
+
return host;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Renders the header on the "server", runs what only the client runs (a
|
|
122
|
+
* `.client` plugin), then hydrates the server markup.
|
|
123
|
+
*/
|
|
124
|
+
async function hydrateHeader(runOnClient: () => void): Promise<HTMLElement> {
|
|
125
|
+
const host = document.createElement("div");
|
|
126
|
+
host.innerHTML = await renderToString(createHeaderApp(createSSRApp));
|
|
127
|
+
document.body.append(host);
|
|
128
|
+
runOnClient();
|
|
129
|
+
app = createHeaderApp(createSSRApp);
|
|
108
130
|
app.mount(host);
|
|
109
131
|
return host;
|
|
110
132
|
}
|
|
111
133
|
|
|
134
|
+
function hydrationWarnings(spies: ReturnType<typeof vi.spyOn>[]): string[] {
|
|
135
|
+
return spies
|
|
136
|
+
.flatMap((spy) => spy.mock.calls.map((call) => String(call[0])))
|
|
137
|
+
.filter((message) => message.includes("Hydration"));
|
|
138
|
+
}
|
|
139
|
+
|
|
112
140
|
function button(host: HTMLElement, label: string): HTMLButtonElement | null {
|
|
113
141
|
return host.querySelector(`button[aria-label="${label}"]`);
|
|
114
142
|
}
|
|
@@ -145,7 +173,7 @@ describe("header action buttons", () => {
|
|
|
145
173
|
action("toggle", { onSelect: vi.fn(), isActive: () => isOn.value }),
|
|
146
174
|
);
|
|
147
175
|
registerHeaderAction(action("plain", { onSelect: vi.fn() }));
|
|
148
|
-
const host = mountHeader();
|
|
176
|
+
const host = await mountHeader();
|
|
149
177
|
const toggle = button(host, "toggle");
|
|
150
178
|
expect(toggle?.dataset.color).toBe(IDLE_COLOR);
|
|
151
179
|
expect(toggle?.getAttribute("aria-pressed")).toBe("false");
|
|
@@ -160,7 +188,7 @@ describe("header action buttons", () => {
|
|
|
160
188
|
registerHeaderAction(
|
|
161
189
|
action("panel", { label: "$panel.toggle", sidePanelId: PANEL_ID }),
|
|
162
190
|
);
|
|
163
|
-
const host = mountHeader();
|
|
191
|
+
const host = await mountHeader();
|
|
164
192
|
const toggle = button(host, "t:panel.toggle");
|
|
165
193
|
const { isOpen } = useSidePanel(PANEL_ID);
|
|
166
194
|
toggle?.click();
|
|
@@ -182,10 +210,28 @@ describe("header action buttons", () => {
|
|
|
182
210
|
isActive: () => true,
|
|
183
211
|
}),
|
|
184
212
|
);
|
|
185
|
-
const host = mountHeader();
|
|
213
|
+
const host = await mountHeader();
|
|
186
214
|
const builder = button(host, "Edit");
|
|
187
215
|
builder?.click();
|
|
188
216
|
expect(onSelect).toHaveBeenCalledOnce();
|
|
189
217
|
expect(builder?.dataset.color).toBe(ACTIVE_COLOR);
|
|
190
218
|
});
|
|
191
219
|
});
|
|
220
|
+
|
|
221
|
+
describe("header action hydration", () => {
|
|
222
|
+
it("draws a client-only action once mounted, not over the server markup", async () => {
|
|
223
|
+
const spies = [
|
|
224
|
+
vi.spyOn(console, "warn").mockImplementation(() => undefined),
|
|
225
|
+
vi.spyOn(console, "error").mockImplementation(() => undefined),
|
|
226
|
+
];
|
|
227
|
+
registerHeaderAction(action("panel", { sidePanelId: PANEL_ID }));
|
|
228
|
+
const host = await hydrateHeader(() =>
|
|
229
|
+
registerHeaderAction(action("client", { onSelect: vi.fn() })),
|
|
230
|
+
);
|
|
231
|
+
expect(hydrationWarnings(spies)).toEqual([]);
|
|
232
|
+
expect(button(host, "panel")).not.toBeNull();
|
|
233
|
+
await nextTick();
|
|
234
|
+
expect(button(host, "client")).not.toBeNull();
|
|
235
|
+
spies.forEach((spy) => spy.mockRestore());
|
|
236
|
+
});
|
|
237
|
+
});
|
|
@@ -51,13 +51,20 @@ afterEach(() => {
|
|
|
51
51
|
});
|
|
52
52
|
|
|
53
53
|
describe("DmsInstantSaveBadge", () => {
|
|
54
|
-
it("
|
|
54
|
+
it("says what the save state is, in en-GB", async () => {
|
|
55
55
|
const { default: InstantSaveBadge } = await import(
|
|
56
56
|
"../layers/dms-ui/app/components/save-bar/InstantSaveBadge.vue"
|
|
57
57
|
);
|
|
58
|
-
|
|
58
|
+
const labels = [
|
|
59
|
+
[undefined, "Saved instantly"],
|
|
60
|
+
["idle", "Saved instantly"],
|
|
61
|
+
["saving", "Saving"],
|
|
62
|
+
["saved", "Saved instantly"],
|
|
63
|
+
["error", "Not saved"],
|
|
64
|
+
] as const;
|
|
65
|
+
for (const [state, label] of labels) {
|
|
59
66
|
await mount({ render: () => h(InstantSaveBadge, { state }) });
|
|
60
|
-
expect(badge()?.textContent?.trim()).toBe(
|
|
67
|
+
expect(badge()?.textContent?.trim()).toBe(label);
|
|
61
68
|
expect(badge()?.dataset.state).toBe(state ?? "idle");
|
|
62
69
|
expect(badge()?.title).toBeTruthy();
|
|
63
70
|
app?.unmount();
|
|
@@ -82,7 +89,7 @@ describe("DmsInstantSaveBadge", () => {
|
|
|
82
89
|
}
|
|
83
90
|
});
|
|
84
91
|
|
|
85
|
-
it("
|
|
92
|
+
it("is translated in French", async () => {
|
|
86
93
|
vi.stubGlobal("useI18n", () => ({ t: translator(uiFr) }));
|
|
87
94
|
const { default: InstantSaveBadge } = await import(
|
|
88
95
|
"../layers/dms-ui/app/components/save-bar/InstantSaveBadge.vue"
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { createApp, h, nextTick, type App } from "vue";
|
|
4
|
+
import ListRow from "../layers/dms-ui/app/components/list-row/ListRow.vue";
|
|
5
|
+
|
|
6
|
+
const LONG_TITLE = "Safari on iPhone 15 with a very long device name";
|
|
7
|
+
|
|
8
|
+
let app: App | undefined;
|
|
9
|
+
let host: HTMLDivElement;
|
|
10
|
+
|
|
11
|
+
async function mount(truncate: boolean) {
|
|
12
|
+
app = createApp({ render: () => h(ListRow, { title: LONG_TITLE, truncate }) });
|
|
13
|
+
app.mount(host);
|
|
14
|
+
await nextTick();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const titleText = () =>
|
|
18
|
+
[...host.querySelectorAll("span")].find(
|
|
19
|
+
(span) => span.textContent?.trim() === LONG_TITLE,
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
vi.stubGlobal("useI18n", () => ({ t: (key: string) => key }));
|
|
24
|
+
host = document.createElement("div");
|
|
25
|
+
document.body.append(host);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
afterEach(() => {
|
|
29
|
+
app?.unmount();
|
|
30
|
+
app = undefined;
|
|
31
|
+
host.remove();
|
|
32
|
+
vi.unstubAllGlobals();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe("DmsListRow truncate", () => {
|
|
36
|
+
it("puts the ellipsis on the title text, not on its flex container", async () => {
|
|
37
|
+
await mount(true);
|
|
38
|
+
expect(titleText()?.classList.contains("truncate")).toBe(true);
|
|
39
|
+
expect(titleText()?.parentElement?.title).toBe(LONG_TITLE);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("leaves the title text alone without truncate", async () => {
|
|
43
|
+
await mount(false);
|
|
44
|
+
expect(titleText()?.classList.contains("truncate")).toBe(false);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { readFileSync, readdirSync, statSync } from "node:fs";
|
|
2
|
-
import { join, relative } from "node:path";
|
|
3
1
|
import { describe, expect, it } from "vitest";
|
|
4
2
|
import coreEn from "../layers/dms-core/i18n/locales/core-en-GB.json";
|
|
5
3
|
import coreFr from "../layers/dms-core/i18n/locales/core-fr-FR.json";
|
|
@@ -12,13 +10,6 @@ const LABELLED_TAGS = {
|
|
|
12
10
|
} as const;
|
|
13
11
|
const LOCALES = { "en-GB": coreEn, "fr-FR": coreFr };
|
|
14
12
|
|
|
15
|
-
function walk(directory: string): string[] {
|
|
16
|
-
return readdirSync(directory).flatMap((name) => {
|
|
17
|
-
const path = join(directory, name);
|
|
18
|
-
return statSync(path).isDirectory() ? walk(path) : [path];
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
|
|
22
13
|
function lookup(messages: unknown, key: string): unknown {
|
|
23
14
|
return key
|
|
24
15
|
.split(".")
|
|
@@ -29,25 +20,7 @@ function lookup(messages: unknown, key: string): unknown {
|
|
|
29
20
|
}
|
|
30
21
|
|
|
31
22
|
describe("Nuxt UI hard-coded aria-labels", () => {
|
|
32
|
-
const
|
|
33
|
-
.filter((path) => path.endsWith(".vue"))
|
|
34
|
-
.map((path) => ({
|
|
35
|
-
path: relative(process.cwd(), path),
|
|
36
|
-
text: readFileSync(path, "utf8"),
|
|
37
|
-
}));
|
|
38
|
-
|
|
39
|
-
for (const [tag, key] of Object.entries(LABELLED_TAGS)) {
|
|
40
|
-
it(`binds a translated aria-label on every ${tag}`, () => {
|
|
41
|
-
const tagPattern = new RegExp(`<${tag}\\b[^>]*?>`, "gs");
|
|
42
|
-
const offenders = sources.flatMap(({ path, text }) =>
|
|
43
|
-
[...text.matchAll(tagPattern)]
|
|
44
|
-
.filter(([found]) => !found.includes(`:aria-label="t('${key}')"`))
|
|
45
|
-
.map(() => path),
|
|
46
|
-
);
|
|
47
|
-
|
|
48
|
-
expect(offenders).toEqual([]);
|
|
49
|
-
});
|
|
50
|
-
|
|
23
|
+
for (const key of Object.values(LABELLED_TAGS)) {
|
|
51
24
|
it(`translates ${key} in every locale`, () => {
|
|
52
25
|
for (const messages of Object.values(LOCALES)) {
|
|
53
26
|
expect(lookup(messages, key)).toBeTypeOf("string");
|
|
@@ -5,8 +5,6 @@
|
|
|
5
5
|
* so nothing refetches on the clock; a period the user picks still applies at
|
|
6
6
|
* once, and resuming catches the skipped minutes up in one read.
|
|
7
7
|
*/
|
|
8
|
-
import { readFileSync } from "node:fs";
|
|
9
|
-
import { resolve } from "node:path";
|
|
10
8
|
import { createApp, defineComponent, h, ref } from "vue";
|
|
11
9
|
import { afterEach, beforeEach, expect, it, vi } from "vitest";
|
|
12
10
|
import { createPageLiveController } from "../layers/dms-ui/app/build/composables/live/pageLiveController";
|
|
@@ -107,14 +105,3 @@ it("holds the period while paused, but follows the user's pick", async () => {
|
|
|
107
105
|
expect(requests).toHaveLength(3);
|
|
108
106
|
expect(period.state.value.key).not.toBe(heldKey);
|
|
109
107
|
});
|
|
110
|
-
|
|
111
|
-
it("holds the selector's period on the page's pause", () => {
|
|
112
|
-
const source = readFileSync(
|
|
113
|
-
resolve(
|
|
114
|
-
__dirname,
|
|
115
|
-
"../layers/dms-ui/app/components/period/PeriodSelector.vue",
|
|
116
|
-
),
|
|
117
|
-
"utf8",
|
|
118
|
-
);
|
|
119
|
-
expect(source).toContain("holdClock: () => isPagePaused.value,");
|
|
120
|
-
});
|
|
@@ -5,8 +5,6 @@
|
|
|
5
5
|
* from the date fields or a long preset is cut to it.
|
|
6
6
|
*/
|
|
7
7
|
import { CalendarDate } from "@internationalized/date";
|
|
8
|
-
import { readFileSync } from "node:fs";
|
|
9
|
-
import { resolve } from "node:path";
|
|
10
8
|
import { createApp, defineComponent, h, nextTick, type App } from "vue";
|
|
11
9
|
import { afterEach, describe, expect, it } from "vitest";
|
|
12
10
|
import PeriodRangeCalendar from "../layers/dms-ui/app/build/components/period/PeriodRangeCalendar.vue";
|
|
@@ -114,15 +112,3 @@ describe("the range calendar with maxDays", () => {
|
|
|
114
112
|
expect(day(host, "2026-09-30").hasAttribute("data-disabled")).toBe(false);
|
|
115
113
|
});
|
|
116
114
|
});
|
|
117
|
-
|
|
118
|
-
it("hands maxDays to the calendar and cuts the date fields to it", () => {
|
|
119
|
-
const source = readFileSync(
|
|
120
|
-
resolve(
|
|
121
|
-
__dirname,
|
|
122
|
-
"../layers/dms-ui/app/components/period/PeriodSelector.vue",
|
|
123
|
-
),
|
|
124
|
-
"utf8",
|
|
125
|
-
);
|
|
126
|
-
expect(source).toContain(':max-days="maxDays"');
|
|
127
|
-
expect(source).toContain("limitCalendarRange(range, props.maxDays, anchor)");
|
|
128
|
-
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// @vitest-environment jsdom
|
|
2
|
+
import { interpolateUrl } from "../layers/dms-core/app/utils/url-interpolation";
|
|
2
3
|
import { afterEach, beforeEach, expect, it, vi } from "vitest";
|
|
3
4
|
import {
|
|
4
5
|
type App,
|
|
@@ -203,9 +204,7 @@ beforeEach(() => {
|
|
|
203
204
|
vi.stubGlobal("useToast", () => ({ add: addToast }));
|
|
204
205
|
vi.stubGlobal("useConfirm", () => ({ confirm }));
|
|
205
206
|
vi.stubGlobal("useAuthFetch", () => ({ $authFetch: authFetch }));
|
|
206
|
-
vi.stubGlobal("interpolateUrl",
|
|
207
|
-
url.replace(/\{(\w+)\}/g, (_, key: string) => data[key] ?? ""),
|
|
208
|
-
);
|
|
207
|
+
vi.stubGlobal("interpolateUrl", interpolateUrl);
|
|
209
208
|
vi.stubGlobal("useTranslation", () => ({
|
|
210
209
|
processApiMessage: (message: string) => `t(${message})`,
|
|
211
210
|
}));
|
|
@@ -828,16 +828,4 @@ describe("open row highlight", () => {
|
|
|
828
828
|
"data-[open=true]:[&>td:first-child]:shadow-[inset_2px_0_0_0_var(--ui-primary)]",
|
|
829
829
|
);
|
|
830
830
|
});
|
|
831
|
-
|
|
832
|
-
it("marks the open row and wires both pointer events to the click mode", () => {
|
|
833
|
-
expect(tableSource).toContain(
|
|
834
|
-
':data-open="isOpenRow(row.original) || undefined"',
|
|
835
|
-
);
|
|
836
|
-
expect(tableSource).toContain(
|
|
837
|
-
'@click="handleRowPointer($event, row.original)"',
|
|
838
|
-
);
|
|
839
|
-
expect(tableSource).toContain(
|
|
840
|
-
'@dblclick="handleRowPointer($event, row.original)"',
|
|
841
|
-
);
|
|
842
|
-
});
|
|
843
831
|
});
|