@antelopejs/dms 0.7.9 → 0.7.10
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 +29 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.js +9 -0
- package/dist/config.js.map +1 -1
- package/dist/implementations/dms/api-base-url.d.ts +1 -0
- package/dist/implementations/dms/api-base-url.js +6 -0
- package/dist/implementations/dms/api-base-url.js.map +1 -0
- package/dist/implementations/dms/frontend-modules.js +7 -6
- package/dist/implementations/dms/frontend-modules.js.map +1 -1
- package/dist/implementations/dms/page.js +8 -2
- package/dist/implementations/dms/page.js.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/test/unit/api-base-url.test.d.ts +1 -0
- package/dist/test/unit/api-base-url.test.js +31 -0
- package/dist/test/unit/api-base-url.test.js.map +1 -0
- package/dist/test/unit/interfaces/dms/page-layout-query.test.d.ts +1 -0
- package/dist/test/unit/interfaces/dms/page-layout-query.test.js +106 -0
- package/dist/test/unit/interfaces/dms/page-layout-query.test.js.map +1 -0
- package/dist/test/unit/interfaces/dms/page-teardown.test.js +1 -0
- package/dist/test/unit/interfaces/dms/page-teardown.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms-base/block-parity-options.test.d.ts +1 -0
- package/dist/test/unit/interfaces/dms-base/block-parity-options.test.js +46 -0
- package/dist/test/unit/interfaces/dms-base/block-parity-options.test.js.map +1 -0
- package/dist/test/unit/interfaces/dms-base/confirm-dialog-secondary-action.test.d.ts +1 -0
- package/dist/test/unit/interfaces/dms-base/confirm-dialog-secondary-action.test.js +26 -0
- package/dist/test/unit/interfaces/dms-base/confirm-dialog-secondary-action.test.js.map +1 -0
- package/dist/test/unit/interfaces/dms-base/data-types/field-types.test.js +10 -0
- package/dist/test/unit/interfaces/dms-base/data-types/field-types.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms-base/guard.test.d.ts +1 -0
- package/dist/test/unit/interfaces/dms-base/guard.test.js +53 -0
- package/dist/test/unit/interfaces/dms-base/guard.test.js.map +1 -0
- package/dist/test/unit/interfaces/dms-base/layout-header-actions.test.js +23 -0
- package/dist/test/unit/interfaces/dms-base/layout-header-actions.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms-base/page-live.test.js +55 -0
- package/dist/test/unit/interfaces/dms-base/page-live.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms-base/period-selector-catalog.test.js +17 -0
- package/dist/test/unit/interfaces/dms-base/period-selector-catalog.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms-base/table-view-reduced-chrome.test.js +2 -0
- 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 +28 -0
- package/dist/test/unit/interfaces/dms-base/table-view-source-reorder.test.js.map +1 -1
- package/frontend-vue/layers/dms-core/app/build/composables/user/preferencesCookie.ts +149 -0
- package/frontend-vue/layers/dms-core/app/build/composables/user/tablePreferences.ts +321 -0
- package/frontend-vue/layers/dms-core/app/composables/period/compareResolvers.ts +4 -3
- package/frontend-vue/layers/dms-core/app/composables/period/presetResolvers.ts +4 -3
- package/frontend-vue/layers/dms-core/app/composables/period/types.ts +3 -1
- package/frontend-vue/layers/dms-core/app/composables/period/usePeriod.ts +2 -2
- package/frontend-vue/layers/dms-core/app/composables/period/usePeriodScope.ts +9 -2
- package/frontend-vue/layers/dms-core/app/composables/realtime/usePageRealtime.ts +48 -16
- package/frontend-vue/layers/dms-core/app/composables/realtime/useUserRealtime.ts +21 -4
- package/frontend-vue/layers/dms-core/app/composables/user/usePreferences.ts +81 -49
- package/frontend-vue/layers/dms-core/app/types/confirm-dialog.ts +35 -0
- package/frontend-vue/layers/dms-core/i18n/locales/core-en-GB.json +2 -0
- package/frontend-vue/layers/dms-core/i18n/locales/core-fr-FR.json +2 -0
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardPageBody.vue +10 -1
- package/frontend-vue/layers/dms-layout/app/build/components/layout/PageHeaderActionBar.vue +32 -1
- package/frontend-vue/layers/dms-layout/app/build/components/layout/PageHeaderActionsOutlet.vue +13 -3
- package/frontend-vue/layers/dms-layout/app/build/components/layout/RowSidePanel.vue +27 -3
- package/frontend-vue/layers/dms-layout/app/build/composables/layout/rowSidePanel.ts +16 -0
- package/frontend-vue/layers/dms-layout/app/composables/layout/useServedHeaderActions.ts +75 -0
- package/frontend-vue/layers/dms-layout/app/composables/page/usePageLayout.ts +8 -1
- package/frontend-vue/layers/dms-layout/app/composables/page/useSiteLayout.ts +13 -2
- package/frontend-vue/layers/dms-layout/app/custom-layouts/DefaultLayout.vue +7 -0
- package/frontend-vue/layers/dms-layout/app/custom-layouts/SettingsLayout.vue +7 -0
- package/frontend-vue/layers/dms-layout/app/pages/[...slug].vue +7 -4
- package/frontend-vue/layers/dms-layout/app/utils/pageQuery.ts +59 -0
- package/frontend-vue/layers/dms-layout/i18n/locales/layout-en-GB.json +5 -1
- package/frontend-vue/layers/dms-layout/i18n/locales/layout-fr-FR.json +5 -1
- package/frontend-vue/layers/dms-ui/app/build/components/activity-feed/activityFeedDays.ts +2 -0
- package/frontend-vue/layers/dms-ui/app/build/components/blocks/BlockInlineStatus.vue +67 -0
- package/frontend-vue/layers/dms-ui/app/build/components/blocks/BlockStatus.vue +60 -20
- package/frontend-vue/layers/dms-ui/app/build/components/table/Actions.vue +15 -8
- package/frontend-vue/layers/dms-ui/app/build/components/table/Empty.vue +14 -0
- package/frontend-vue/layers/dms-ui/app/build/components/table/Table.vue +29 -4
- package/frontend-vue/layers/dms-ui/app/build/composables/actions/useActionTargets.ts +76 -28
- package/frontend-vue/layers/dms-ui/app/build/composables/blocks/useBlockItems.ts +26 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/blocks/useGuard.ts +115 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/confirm/actionError.ts +1 -1
- package/frontend-vue/layers/dms-ui/app/build/composables/confirm/confirmDialogTexts.ts +58 -3
- package/frontend-vue/layers/dms-ui/app/build/composables/confirm/secondaryTarget.ts +86 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/confirm/useActionConfirm.ts +5 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/live/currentPageLive.ts +6 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/live/liveCadence.ts +42 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/live/liveHolds.ts +66 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/live/livePoller.ts +42 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/live/liveStorage.ts +42 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/live/missedTally.ts +65 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/live/pageLiveController.ts +247 -88
- package/frontend-vue/layers/dms-ui/app/build/composables/live/useLivePeriodSpan.ts +33 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/live/usePageLiveHost.ts +79 -5
- package/frontend-vue/layers/dms-ui/app/build/composables/period/periodComparisonControl.ts +70 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/period/periodDisplay.ts +55 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/period/periodQuery.ts +41 -5
- package/frontend-vue/layers/dms-ui/app/build/composables/period/usePeriodDraft.ts +22 -9
- package/frontend-vue/layers/dms-ui/app/build/composables/period/usePeriodLocation.ts +13 -6
- package/frontend-vue/layers/dms-ui/app/build/composables/table/useTable.ts +1 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table/useTableColumns.ts +27 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/clearTableFilters.ts +18 -3
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/usePersistedPagination.ts +19 -26
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useScrolledPastTop.ts +66 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableListData.ts +7 -1
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableLiveHold.ts +65 -11
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTablePreferences.ts +149 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableRows.ts +39 -8
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViewConfig.ts +1 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViewRowActions.ts +16 -11
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViews.ts +39 -16
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/pinnedRows.ts +29 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/recordLink.ts +18 -5
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/sortableColumns.ts +17 -0
- package/frontend-vue/layers/dms-ui/app/build/utils/fetchNotFound.ts +79 -0
- package/frontend-vue/layers/dms-ui/app/build/utils/urlVariables.ts +46 -1
- package/frontend-vue/layers/dms-ui/app/components/activity/ActivityItem.vue +4 -0
- package/frontend-vue/layers/dms-ui/app/components/activity-feed/ActivityFeed.vue +40 -2
- package/frontend-vue/layers/dms-ui/app/components/blocks/CodeBlock.vue +11 -1
- package/frontend-vue/layers/dms-ui/app/components/blocks/KeyValueListBlock.vue +12 -2
- package/frontend-vue/layers/dms-ui/app/components/blocks/LiveToggleBlock.vue +129 -4
- package/frontend-vue/layers/dms-ui/app/components/blocks/NavCardGridBlock.vue +6 -0
- package/frontend-vue/layers/dms-ui/app/components/blocks/StatGroupBlock.vue +6 -1
- package/frontend-vue/layers/dms-ui/app/components/confirm/ConfirmModal.vue +33 -0
- package/frontend-vue/layers/dms-ui/app/components/form/components/ChoiceCards.vue +22 -4
- package/frontend-vue/layers/dms-ui/app/components/guard/Guard.vue +74 -0
- package/frontend-vue/layers/dms-ui/app/components/kpi/KpiCard.vue +12 -17
- package/frontend-vue/layers/dms-ui/app/components/list-row/ListRow.vue +15 -0
- package/frontend-vue/layers/dms-ui/app/components/meter/MeterBlock.vue +12 -15
- package/frontend-vue/layers/dms-ui/app/components/period/PeriodSelector.vue +104 -16
- package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +104 -98
- package/frontend-vue/layers/dms-ui/app/components/top-list/TopListCard.vue +11 -1
- package/frontend-vue/layers/dms-ui/app/composables/chart/types.ts +2 -0
- package/frontend-vue/layers/dms-ui/app/composables/chart/useChartFetch.ts +11 -1
- package/frontend-vue/layers/dms-ui/app/composables/confirm/types.ts +15 -1
- package/frontend-vue/layers/dms-ui/app/composables/live/usePageLive.ts +86 -10
- package/frontend-vue/layers/dms-ui/app/composables/table-view/shortcuts/tableViewShiftF.ts +20 -0
- package/frontend-vue/layers/dms-ui/app/composables/table-view/types/action-target.ts +12 -0
- package/frontend-vue/layers/dms-ui/app/composables/table-view/types/config.ts +5 -0
- package/frontend-vue/layers/dms-ui/app/composables/table-view/useTableViewEmptyState.ts +55 -0
- package/frontend-vue/layers/dms-ui/app/types/live.ts +47 -1
- package/frontend-vue/layers/dms-ui/i18n/locales/ui-en-GB.json +13 -4
- package/frontend-vue/layers/dms-ui/i18n/locales/ui-fr-FR.json +12 -3
- package/frontend-vue/tests/activity-feed-fill-height.test.ts +1 -0
- package/frontend-vue/tests/activity-feed-head.test.ts +140 -0
- package/frontend-vue/tests/block-not-found.test.ts +220 -0
- package/frontend-vue/tests/choice-cards-columns.test.ts +60 -0
- package/frontend-vue/tests/confirm-secondary-action.test.ts +269 -0
- package/frontend-vue/tests/guard.test.ts +242 -0
- package/frontend-vue/tests/header-action-page-query.test.ts +286 -0
- package/frontend-vue/tests/live-toggle.test.ts +87 -1
- package/frontend-vue/tests/page-layout-registration.test.ts +1 -0
- package/frontend-vue/tests/page-live-parity.test.ts +367 -0
- package/frontend-vue/tests/page-realtime-remount.test.ts +185 -0
- package/frontend-vue/tests/period-selector-parity.test.ts +138 -0
- package/frontend-vue/tests/period-selector-url.test.ts +84 -6
- package/frontend-vue/tests/period-url-query.test.ts +52 -0
- package/frontend-vue/tests/preferences-cookie-size.test.ts +311 -0
- package/frontend-vue/tests/row-side-panel.test.ts +109 -10
- package/frontend-vue/tests/table-actions-search-label.test.ts +57 -1
- package/frontend-vue/tests/table-clear-filters.test.ts +99 -1
- package/frontend-vue/tests/table-density-menu.test.ts +4 -3
- package/frontend-vue/tests/table-live-hold.test.ts +95 -5
- package/frontend-vue/tests/table-row-action-labels.test.ts +181 -0
- package/frontend-vue/tests/table-source-period-scope.test.ts +31 -3
- package/frontend-vue/tests/table-view-hidden-filters-page.test.ts +14 -17
- package/frontend-vue/tests/table-view-pinned-rows.test.ts +143 -0
- package/frontend-vue/tests/table-view-sortable-columns.test.ts +45 -0
- package/frontend-vue/tests/table-view-views.test.ts +56 -15
- package/frontend-vue/tests/top-list-card-value-suffix.test.ts +107 -0
- package/frontend-vue/tests/vue-inventory.test.ts +1 -1
- package/package.json +2 -2
- package/skills/dms/SKILL.md +9 -3
- package/skills/dms-module/SKILL.md +5 -0
- package/skills/dms-pages/REFERENCE.md +23 -2
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A Guard reads a route and, when its condition holds (the value it names is
|
|
3
|
+
* empty, or the route answers 404), draws one EmptyState instead of its
|
|
4
|
+
* children; it keeps them while the route loads, after any other failure,
|
|
5
|
+
* and once the value is set.
|
|
6
|
+
*/
|
|
7
|
+
import { computed, createSSRApp, defineComponent, h, ref } from "vue";
|
|
8
|
+
import { renderToString } from "vue/server-renderer";
|
|
9
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
10
|
+
import Guard from "../layers/dms-ui/app/components/guard/Guard.vue";
|
|
11
|
+
import {
|
|
12
|
+
isEmptyValue,
|
|
13
|
+
readFieldPath,
|
|
14
|
+
} from "../layers/dms-ui/app/build/composables/blocks/useGuard";
|
|
15
|
+
|
|
16
|
+
const answer = ref<unknown>(null);
|
|
17
|
+
const failure = ref<unknown>(null);
|
|
18
|
+
const fetchOptions = vi.fn();
|
|
19
|
+
|
|
20
|
+
vi.mock("../layers/dms-ui/app/composables/chart/useChartFetch", () => ({
|
|
21
|
+
useChartFetch: (options: unknown) => {
|
|
22
|
+
fetchOptions(options);
|
|
23
|
+
return { data: answer, isLoading: ref(false), error: failure };
|
|
24
|
+
},
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
vi.mock("../layers/dms-core/app/composables/watch/useWatch", () => ({
|
|
28
|
+
useWatch: () => ({ state: ref({}) }),
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
const Slot = defineComponent({
|
|
32
|
+
setup:
|
|
33
|
+
(_, { slots }) =>
|
|
34
|
+
() =>
|
|
35
|
+
h("div", slots.default?.()),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const Button = defineComponent({
|
|
39
|
+
props: { label: String, to: String },
|
|
40
|
+
setup: (props) => () =>
|
|
41
|
+
h("a", { "data-button": "", href: props.to }, props.label),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const GUARD = {
|
|
45
|
+
fetchUrl: "/api/routes/activity?route={{query.route}}",
|
|
46
|
+
periodScope: "api",
|
|
47
|
+
realtimeTopic: "requests",
|
|
48
|
+
field: "summary.calls",
|
|
49
|
+
fallback: {
|
|
50
|
+
title: "$api.routes.statistics.idle_title",
|
|
51
|
+
icon: "i-ph-moon-stars",
|
|
52
|
+
actions: [{ label: "Test this route", to: "/routes?tab=2" }],
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
async function render(props: Record<string, unknown>): Promise<string> {
|
|
57
|
+
const app = createSSRApp({
|
|
58
|
+
render: () =>
|
|
59
|
+
h(
|
|
60
|
+
Guard,
|
|
61
|
+
{ componentId: "statistics", pageId: "routes", ...props },
|
|
62
|
+
{ default: () => h("section", { "data-children": "" }, "children") },
|
|
63
|
+
),
|
|
64
|
+
});
|
|
65
|
+
for (const name of ["UIcon", "DmsLink"]) app.component(name, Slot);
|
|
66
|
+
app.component("UButton", Button);
|
|
67
|
+
return renderToString(app);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
beforeEach(() => {
|
|
71
|
+
answer.value = null;
|
|
72
|
+
failure.value = null;
|
|
73
|
+
fetchOptions.mockClear();
|
|
74
|
+
// Nuxt auto-imports, which the components use unimported.
|
|
75
|
+
vi.stubGlobal("ref", ref);
|
|
76
|
+
vi.stubGlobal("computed", computed);
|
|
77
|
+
vi.stubGlobal("useI18n", () => ({
|
|
78
|
+
locale: ref("en-US"),
|
|
79
|
+
t: (key: string) => `t(${key})`,
|
|
80
|
+
}));
|
|
81
|
+
vi.stubGlobal("useTranslation", () => ({
|
|
82
|
+
processI18n: (text: string) => (text.startsWith("$") ? `t(${text})` : text),
|
|
83
|
+
}));
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
afterEach(() => vi.unstubAllGlobals());
|
|
87
|
+
|
|
88
|
+
describe("Guard", () => {
|
|
89
|
+
it("reads the route like a block's data source", async () => {
|
|
90
|
+
await render({ ...GUARD });
|
|
91
|
+
expect(fetchOptions).toHaveBeenCalledWith(
|
|
92
|
+
expect.objectContaining({
|
|
93
|
+
fetchUrl: GUARD.fetchUrl,
|
|
94
|
+
periodScope: "api",
|
|
95
|
+
realtimeTopic: "requests",
|
|
96
|
+
}),
|
|
97
|
+
);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("keeps its children until the route answers", async () => {
|
|
101
|
+
const html = await render({ ...GUARD });
|
|
102
|
+
expect(html).toContain("data-children");
|
|
103
|
+
expect(html).not.toContain("idle_title");
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("keeps its children while the value is set", async () => {
|
|
107
|
+
answer.value = { summary: { calls: 12 } };
|
|
108
|
+
const html = await render({ ...GUARD });
|
|
109
|
+
expect(html).toContain("data-children");
|
|
110
|
+
expect(html).not.toContain("idle_title");
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("keeps its children after a failure", async () => {
|
|
114
|
+
answer.value = { summary: { calls: 0 } };
|
|
115
|
+
failure.value = { statusCode: 500 };
|
|
116
|
+
const html = await render({ ...GUARD });
|
|
117
|
+
expect(html).toContain("data-children");
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("swaps its children for the fallback when the value is empty", async () => {
|
|
121
|
+
answer.value = { summary: { calls: 0 } };
|
|
122
|
+
const html = await render({ ...GUARD });
|
|
123
|
+
expect(html).not.toContain("data-children");
|
|
124
|
+
expect(html).toContain("t($api.routes.statistics.idle_title)");
|
|
125
|
+
expect(html).toMatch(
|
|
126
|
+
/<a data-button href="\/routes\?tab=2"[^>]*>Test this route</,
|
|
127
|
+
);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("checks the whole answer without a field", async () => {
|
|
131
|
+
answer.value = [];
|
|
132
|
+
const html = await render({ ...GUARD, field: undefined });
|
|
133
|
+
expect(html).not.toContain("data-children");
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("lays the answer's own emptyState over the fallback", async () => {
|
|
137
|
+
answer.value = {
|
|
138
|
+
summary: { calls: 0 },
|
|
139
|
+
emptyState: {
|
|
140
|
+
description: "Last called Oct 10",
|
|
141
|
+
actions: [{ label: "Test this route", to: "/routes?route=r1&tab=2" }],
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
const html = await render({ ...GUARD });
|
|
145
|
+
expect(html).toContain("t($api.routes.statistics.idle_title)");
|
|
146
|
+
expect(html).toContain("Last called Oct 10");
|
|
147
|
+
expect(html).toMatch(
|
|
148
|
+
/<a data-button href="\/routes\?route=r1&tab=2"[^>]*>/,
|
|
149
|
+
);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("falls back on a translated title without a fallback", async () => {
|
|
153
|
+
answer.value = { summary: { calls: 0 } };
|
|
154
|
+
const html = await render({ ...GUARD, fallback: undefined });
|
|
155
|
+
expect(html).toContain("t(dms.blocks.empty)");
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
describe("when notFound", () => {
|
|
159
|
+
const NOT_FOUND = {
|
|
160
|
+
fetchUrl: "/api/routes/{{params.id}}",
|
|
161
|
+
when: "notFound",
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
it("swaps its children for Not found when the route answers 404", async () => {
|
|
165
|
+
failure.value = { statusCode: 404 };
|
|
166
|
+
const html = await render(NOT_FOUND);
|
|
167
|
+
expect(html).not.toContain("data-children");
|
|
168
|
+
expect(html).toContain("t(dms.blocks.not_found)");
|
|
169
|
+
expect(html).toContain("t(dms.blocks.not_found_hint)");
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("describes it with the 404's message, as a block does", async () => {
|
|
173
|
+
failure.value = {
|
|
174
|
+
statusCode: 404,
|
|
175
|
+
data: { message: "No such route" },
|
|
176
|
+
};
|
|
177
|
+
const html = await render(NOT_FOUND);
|
|
178
|
+
expect(html).toContain("t(dms.blocks.not_found)");
|
|
179
|
+
expect(html).toContain("No such route");
|
|
180
|
+
expect(html).not.toContain("t(dms.blocks.not_found_hint)");
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("reads the 404 body ofetch keeps on the response", async () => {
|
|
184
|
+
failure.value = {
|
|
185
|
+
response: {
|
|
186
|
+
status: 404,
|
|
187
|
+
_data: { emptyState: { title: "Workspace gone" } },
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
expect(await render(NOT_FOUND)).toContain("Workspace gone");
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it("lets the fallback replace the 404's message", async () => {
|
|
194
|
+
failure.value = { statusCode: 404, data: { message: "No such route" } };
|
|
195
|
+
const html = await render({
|
|
196
|
+
...NOT_FOUND,
|
|
197
|
+
fallback: { description: "This route was removed." },
|
|
198
|
+
});
|
|
199
|
+
expect(html).toContain("This route was removed.");
|
|
200
|
+
expect(html).not.toContain("No such route");
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it("lays the 404 body's emptyState over the fallback", async () => {
|
|
204
|
+
failure.value = {
|
|
205
|
+
statusCode: 404,
|
|
206
|
+
data: { emptyState: { description: "Deleted on Oct 10" } },
|
|
207
|
+
};
|
|
208
|
+
const html = await render({
|
|
209
|
+
...NOT_FOUND,
|
|
210
|
+
fallback: { title: "Route gone" },
|
|
211
|
+
});
|
|
212
|
+
expect(html).toContain("Route gone");
|
|
213
|
+
expect(html).toContain("Deleted on Oct 10");
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it("keeps its children on an answer or another failure", async () => {
|
|
217
|
+
answer.value = null;
|
|
218
|
+
expect(await render(NOT_FOUND)).toContain("data-children");
|
|
219
|
+
answer.value = { id: "r1" };
|
|
220
|
+
expect(await render(NOT_FOUND)).toContain("data-children");
|
|
221
|
+
failure.value = { statusCode: 500 };
|
|
222
|
+
expect(await render(NOT_FOUND)).toContain("data-children");
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
describe("Guard field", () => {
|
|
228
|
+
it("reads a dot path, stopping where it ends", () => {
|
|
229
|
+
expect(readFieldPath({ a: { b: 3 } }, "a.b")).toBe(3);
|
|
230
|
+
expect(readFieldPath({ a: null }, "a.b")).toBeUndefined();
|
|
231
|
+
expect(readFieldPath({ calls: 0 }, "calls")).toBe(0);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it("calls absent, null, false, 0, empty text and empty lists empty", () => {
|
|
235
|
+
for (const value of [undefined, null, false, 0, "", []]) {
|
|
236
|
+
expect(isEmptyValue(value)).toBe(true);
|
|
237
|
+
}
|
|
238
|
+
for (const value of [1, true, "x", [0], {}]) {
|
|
239
|
+
expect(isEmptyValue(value)).toBe(false);
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
});
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
/**
|
|
3
|
+
* A page header's buttons follow the page URL: a target URL takes the
|
|
4
|
+
* `{{params.X}}` / `{{query.X}}` tokens of a block's `fetchUrl`, and a
|
|
5
|
+
* button whose availability reads the query is asked for again when the
|
|
6
|
+
* query changes.
|
|
7
|
+
*/
|
|
8
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
9
|
+
import {
|
|
10
|
+
computed,
|
|
11
|
+
createApp,
|
|
12
|
+
defineComponent,
|
|
13
|
+
h,
|
|
14
|
+
nextTick,
|
|
15
|
+
reactive,
|
|
16
|
+
ref,
|
|
17
|
+
type App,
|
|
18
|
+
type FunctionalComponent,
|
|
19
|
+
} from "vue";
|
|
20
|
+
import { resolveLinkVariables } from "../layers/dms-ui/app/build/utils/urlVariables";
|
|
21
|
+
import {
|
|
22
|
+
isSamePageQuery,
|
|
23
|
+
layoutUrlWithQuery,
|
|
24
|
+
pageQueryValues,
|
|
25
|
+
} from "../layers/dms-layout/app/utils/pageQuery";
|
|
26
|
+
|
|
27
|
+
const handleCustomButton = vi.fn();
|
|
28
|
+
vi.mock("#dms-ui/app/build/composables/actions/useActionTargets", () => ({
|
|
29
|
+
useActionTargets: () => ({ handleCustomButton }),
|
|
30
|
+
}));
|
|
31
|
+
vi.mock("#dms-core/app/build/composables/auth/usePermissionPreview", () => ({
|
|
32
|
+
usePermissionPreview: () => ({
|
|
33
|
+
isHeaderActionHidden: () => false,
|
|
34
|
+
session: ref(null),
|
|
35
|
+
}),
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
const LAYOUT_URL = "/modules/lang/queue/pagelayout";
|
|
39
|
+
const route = reactive<{ path: string; query: Record<string, unknown> }>({
|
|
40
|
+
path: "/modules/lang/queue/de",
|
|
41
|
+
query: {},
|
|
42
|
+
});
|
|
43
|
+
const authFetch = vi.fn();
|
|
44
|
+
|
|
45
|
+
const ButtonStub = defineComponent({
|
|
46
|
+
props: { label: String, disabled: Boolean, onClick: Function, to: String },
|
|
47
|
+
setup: (props) => () =>
|
|
48
|
+
h(
|
|
49
|
+
"button",
|
|
50
|
+
{
|
|
51
|
+
disabled: props.disabled,
|
|
52
|
+
"data-to": props.to,
|
|
53
|
+
onClick: () => props.onClick?.(),
|
|
54
|
+
},
|
|
55
|
+
props.label,
|
|
56
|
+
),
|
|
57
|
+
});
|
|
58
|
+
const SlotStub: FunctionalComponent = (_, { slots }) => slots.default?.();
|
|
59
|
+
|
|
60
|
+
let app: App | undefined;
|
|
61
|
+
let host: HTMLDivElement;
|
|
62
|
+
|
|
63
|
+
beforeEach(() => {
|
|
64
|
+
route.query = { workspace: "web" };
|
|
65
|
+
handleCustomButton.mockClear();
|
|
66
|
+
authFetch.mockReset();
|
|
67
|
+
vi.stubGlobal("useAuthFetch", () => ({ $authFetch: authFetch }));
|
|
68
|
+
vi.stubGlobal("computed", computed);
|
|
69
|
+
vi.stubGlobal("useTranslation", () => ({
|
|
70
|
+
processI18n: (value: string) => value,
|
|
71
|
+
}));
|
|
72
|
+
vi.stubGlobal("useI18n", () => ({ t: (key: string) => key }));
|
|
73
|
+
vi.stubGlobal("useDmsRoute", () => route);
|
|
74
|
+
vi.stubGlobal("useDmsRouter", () => ({ replace: vi.fn() }));
|
|
75
|
+
vi.stubGlobal("useSiteLayout", () => ({
|
|
76
|
+
quickActions: ref(null),
|
|
77
|
+
findMatchingRoute: () => ({
|
|
78
|
+
metadata: { layoutUrl: LAYOUT_URL },
|
|
79
|
+
params: { locale: "de" },
|
|
80
|
+
}),
|
|
81
|
+
}));
|
|
82
|
+
host = document.createElement("div");
|
|
83
|
+
document.body.append(host);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
afterEach(() => {
|
|
87
|
+
app?.unmount();
|
|
88
|
+
app = undefined;
|
|
89
|
+
host.remove();
|
|
90
|
+
vi.unstubAllGlobals();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
async function mount(path: string, props: Record<string, unknown>) {
|
|
94
|
+
const { default: component } = await import(path);
|
|
95
|
+
app = createApp({ render: () => h(component, props) });
|
|
96
|
+
app.component("UButton", ButtonStub);
|
|
97
|
+
app.component("UTooltip", SlotStub);
|
|
98
|
+
app.mount(host);
|
|
99
|
+
await nextTick();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const mountBar = (actions: unknown[]) =>
|
|
103
|
+
mount(
|
|
104
|
+
"../layers/dms-layout/app/build/components/layout/PageHeaderActionBar.vue",
|
|
105
|
+
{ actions },
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
const buttons = () => [...host.querySelectorAll("button")];
|
|
109
|
+
|
|
110
|
+
describe("resolveLinkVariables", () => {
|
|
111
|
+
const context = {
|
|
112
|
+
routeParams: { id: "ws 1" },
|
|
113
|
+
routeQuery: { workspace: "web", tab: "a&b" },
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
it("fills the page's params and query, encoded", () => {
|
|
117
|
+
expect(
|
|
118
|
+
resolveLinkVariables(
|
|
119
|
+
"/ws/{{params.id}}?workspace={{query.workspace}}&tab={{query.tab}}",
|
|
120
|
+
context,
|
|
121
|
+
),
|
|
122
|
+
).toBe("/ws/ws%201?workspace=web&tab=a%26b");
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("leaves out a query pair the page has no value for", () => {
|
|
126
|
+
expect(
|
|
127
|
+
resolveLinkVariables(
|
|
128
|
+
"/t?missing={{query.locale}}&workspace={{query.workspace}}#keys",
|
|
129
|
+
context,
|
|
130
|
+
),
|
|
131
|
+
).toBe("/t?workspace=web#keys");
|
|
132
|
+
expect(resolveLinkVariables("/t?locale={{query.locale}}", context)).toBe(
|
|
133
|
+
"/t",
|
|
134
|
+
);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("fills a query key carrying dots", () => {
|
|
138
|
+
expect(
|
|
139
|
+
resolveLinkVariables("/logs?period={{query.api.period}}", {
|
|
140
|
+
routeQuery: { "api.period": "7d" },
|
|
141
|
+
}),
|
|
142
|
+
).toBe("/logs?period=7d");
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("empties any other token left without a value", () => {
|
|
146
|
+
expect(resolveLinkVariables("/t/{{params.slug}}/edit", context)).toBe(
|
|
147
|
+
"/t//edit",
|
|
148
|
+
);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe("page query helpers", () => {
|
|
153
|
+
it("reads one value per name, the last, as the server does", () => {
|
|
154
|
+
expect(
|
|
155
|
+
pageQueryValues({ workspace: "web", tag: ["a", "b"], empty: null }),
|
|
156
|
+
).toEqual({ workspace: "web", tag: "b" });
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("compares queries in any order", () => {
|
|
160
|
+
expect(isSamePageQuery({ a: "1", b: "2" }, { b: "2", a: "1" })).toBe(true);
|
|
161
|
+
expect(isSamePageQuery({ a: "1" }, { a: "1", b: "2" })).toBe(false);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("leaves out the keys that only record the view's state", () => {
|
|
165
|
+
const base = { workspace: "shop" };
|
|
166
|
+
expect(
|
|
167
|
+
isSamePageQuery(base, {
|
|
168
|
+
workspace: "shop",
|
|
169
|
+
record: "42",
|
|
170
|
+
live: "0",
|
|
171
|
+
"api.period": "24h",
|
|
172
|
+
"api.compare": "previous",
|
|
173
|
+
"api.from": "2026-10-01",
|
|
174
|
+
}),
|
|
175
|
+
).toBe(true);
|
|
176
|
+
expect(isSamePageQuery(base, { workspace: "blog", record: "42" })).toBe(
|
|
177
|
+
false,
|
|
178
|
+
);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("asks the layout URL for a query", () => {
|
|
182
|
+
expect(layoutUrlWithQuery("/p/pagelayout", { workspace: "a b" })).toBe(
|
|
183
|
+
"/p/pagelayout?workspace=a+b",
|
|
184
|
+
);
|
|
185
|
+
expect(layoutUrlWithQuery("/p/pagelayout", {})).toBe("/p/pagelayout");
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
describe("a header button's target URL", () => {
|
|
190
|
+
it("links to the page's scope, and follows it", async () => {
|
|
191
|
+
await mountBar([
|
|
192
|
+
{
|
|
193
|
+
id: "matrix",
|
|
194
|
+
label: "Open matrix",
|
|
195
|
+
target: {
|
|
196
|
+
type: "page",
|
|
197
|
+
url: "/modules/lang/translations?workspace={{query.workspace}}&missingIn={{params.locale}}",
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
]);
|
|
201
|
+
expect(buttons()[0]?.dataset.to).toBe(
|
|
202
|
+
"/modules/lang/translations?workspace=web&missingIn=de",
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
route.query = { workspace: "docs" };
|
|
206
|
+
await nextTick();
|
|
207
|
+
expect(buttons()[0]?.dataset.to).toBe(
|
|
208
|
+
"/modules/lang/translations?workspace=docs&missingIn=de",
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
route.query = {};
|
|
212
|
+
await nextTick();
|
|
213
|
+
expect(buttons()[0]?.dataset.to).toBe(
|
|
214
|
+
"/modules/lang/translations?missingIn=de",
|
|
215
|
+
);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it("runs an API target on the page's record", async () => {
|
|
219
|
+
await mountBar([
|
|
220
|
+
{
|
|
221
|
+
id: "export",
|
|
222
|
+
label: "Export",
|
|
223
|
+
target: {
|
|
224
|
+
type: "api",
|
|
225
|
+
url: "/api/lang/export?workspace={{query.workspace}}",
|
|
226
|
+
successMessage: "Done",
|
|
227
|
+
},
|
|
228
|
+
confirm: { title: "Export?" },
|
|
229
|
+
},
|
|
230
|
+
]);
|
|
231
|
+
buttons()[0]?.click();
|
|
232
|
+
expect(handleCustomButton).toHaveBeenCalledWith(
|
|
233
|
+
expect.objectContaining({
|
|
234
|
+
target: expect.objectContaining({
|
|
235
|
+
url: "/api/lang/export?workspace=web",
|
|
236
|
+
}),
|
|
237
|
+
}),
|
|
238
|
+
);
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
describe("a header button's availability", () => {
|
|
243
|
+
const download = (disabled: boolean) => ({
|
|
244
|
+
id: "download",
|
|
245
|
+
label: "Download",
|
|
246
|
+
target: { type: "page", url: "/download" },
|
|
247
|
+
disabled,
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
const mountOutlet = (servedQuery?: Record<string, string>) =>
|
|
251
|
+
mount(
|
|
252
|
+
"../layers/dms-layout/app/build/components/layout/PageHeaderActionsOutlet.vue",
|
|
253
|
+
{ actions: [download(false)], servedQuery },
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
it("is not asked again while the query is the one it was served for", async () => {
|
|
257
|
+
await mountOutlet({ workspace: "web" });
|
|
258
|
+
expect(authFetch).not.toHaveBeenCalled();
|
|
259
|
+
expect(buttons()[0]?.disabled).toBe(false);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it("is asked again for the page's query when it changes", async () => {
|
|
263
|
+
authFetch.mockResolvedValue({
|
|
264
|
+
components: {},
|
|
265
|
+
layout: { options: { headerActions: [download(true)] } },
|
|
266
|
+
});
|
|
267
|
+
await mountOutlet({ workspace: "web" });
|
|
268
|
+
|
|
269
|
+
route.query = { workspace: "gone" };
|
|
270
|
+
await nextTick();
|
|
271
|
+
await new Promise((resolve) => setTimeout(resolve));
|
|
272
|
+
expect(authFetch).toHaveBeenCalledWith(`${LAYOUT_URL}?workspace=gone`);
|
|
273
|
+
expect(buttons()[0]?.disabled).toBe(true);
|
|
274
|
+
|
|
275
|
+
route.query = { workspace: "web" };
|
|
276
|
+
await nextTick();
|
|
277
|
+
expect(buttons()[0]?.disabled).toBe(false);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it("is never asked again when no button declares one", async () => {
|
|
281
|
+
await mountOutlet(undefined);
|
|
282
|
+
route.query = { workspace: "gone" };
|
|
283
|
+
await nextTick();
|
|
284
|
+
expect(authFetch).not.toHaveBeenCalled();
|
|
285
|
+
});
|
|
286
|
+
});
|
|
@@ -5,7 +5,10 @@
|
|
|
5
5
|
* text field, and nothing at all on a page without `live`.
|
|
6
6
|
*/
|
|
7
7
|
import { createApp, h, nextTick, type App } from "vue";
|
|
8
|
-
import {
|
|
8
|
+
import { readFileSync } from "node:fs";
|
|
9
|
+
import { resolve } from "node:path";
|
|
10
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
11
|
+
import { usePageLive } from "../layers/dms-ui/app/composables/live/usePageLive";
|
|
9
12
|
import { defineShortcuts } from "@nuxt/ui/runtime/composables/defineShortcuts.js";
|
|
10
13
|
import {
|
|
11
14
|
createPageLiveController,
|
|
@@ -33,6 +36,9 @@ async function createToggle(
|
|
|
33
36
|
h(LiveToggleBlock, { componentId: "live", pageId: "api.logs", ...props }),
|
|
34
37
|
});
|
|
35
38
|
toggle.component("UIcon", () => h("i"));
|
|
39
|
+
toggle.component("UButton", (buttonProps: { label?: string }, { attrs }) =>
|
|
40
|
+
h("button", { ...attrs, type: "button" }, buttonProps.label),
|
|
41
|
+
);
|
|
36
42
|
toggle.mount(target);
|
|
37
43
|
await nextTick();
|
|
38
44
|
return toggle;
|
|
@@ -160,3 +166,83 @@ it("switches once per L with two toggles on the page", async () => {
|
|
|
160
166
|
expect(controller.isPaused.value).toBe(false);
|
|
161
167
|
second.unmount();
|
|
162
168
|
});
|
|
169
|
+
|
|
170
|
+
describe("the pill variant and the paused state", () => {
|
|
171
|
+
const pill = (value: string) =>
|
|
172
|
+
host.querySelector<HTMLButtonElement>(`button[data-value="${value}"]`);
|
|
173
|
+
const resumeButton = () =>
|
|
174
|
+
host.querySelector<HTMLButtonElement>('[data-action="resume"]');
|
|
175
|
+
|
|
176
|
+
it("draws a Live pill with a pulsing dot beside Pause", async () => {
|
|
177
|
+
installLive();
|
|
178
|
+
await mountToggle({ variant: "pill" });
|
|
179
|
+
expect(pill("direct")?.getAttribute("aria-pressed")).toBe("true");
|
|
180
|
+
expect(pill("direct")?.textContent).toContain("dms.live.direct");
|
|
181
|
+
expect(host.querySelector(".dms-live-toggle__dot")?.className).toContain(
|
|
182
|
+
"animate-pulse",
|
|
183
|
+
);
|
|
184
|
+
expect(host.textContent).toContain("dms.live.updated_seconds:0");
|
|
185
|
+
expect(resumeButton()).toBeNull();
|
|
186
|
+
|
|
187
|
+
pill("pause")?.click();
|
|
188
|
+
await nextTick();
|
|
189
|
+
expect(live?.isPaused.value).toBe(true);
|
|
190
|
+
expect(resumeButton()).not.toBeNull();
|
|
191
|
+
|
|
192
|
+
resumeButton()?.click();
|
|
193
|
+
await nextTick();
|
|
194
|
+
expect(live?.isPaused.value).toBe(false);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("says what was missed, with the module's label and breakdown", async () => {
|
|
198
|
+
const controller = installLive();
|
|
199
|
+
await mountToggle({
|
|
200
|
+
variant: "pill",
|
|
201
|
+
missedLabel: "$api.live.new_requests",
|
|
202
|
+
});
|
|
203
|
+
controller.pause();
|
|
204
|
+
const block = controller.subscribe(() => {});
|
|
205
|
+
block.trigger({ topic: "api", payload: { count: 40 } });
|
|
206
|
+
usePageLive().setMissedDetail({
|
|
207
|
+
text: "$api.live.in_5xx",
|
|
208
|
+
params: { count: 2 },
|
|
209
|
+
tone: "error",
|
|
210
|
+
});
|
|
211
|
+
await nextTick();
|
|
212
|
+
|
|
213
|
+
const status = host.querySelector(".dms-live-toggle__status");
|
|
214
|
+
expect(status?.textContent).toContain(
|
|
215
|
+
"dms.live.paused · api.live.new_requests:40",
|
|
216
|
+
);
|
|
217
|
+
expect(status?.querySelector(".text-error")?.textContent).toContain(
|
|
218
|
+
"api.live.in_5xx:2",
|
|
219
|
+
);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it("names the reason a component holds the page for", async () => {
|
|
223
|
+
installLive();
|
|
224
|
+
await mountToggle();
|
|
225
|
+
usePageLive().hold("$api.live.typing_in_tester");
|
|
226
|
+
await nextTick();
|
|
227
|
+
expect(host.textContent).toContain(
|
|
228
|
+
"dms.live.paused · api.live.typing_in_tester",
|
|
229
|
+
);
|
|
230
|
+
expect(host.textContent).not.toContain("dms.live.updated");
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it("reads Live in en-GB, Direct in fr-FR", () => {
|
|
234
|
+
const locale = (name: string) =>
|
|
235
|
+
JSON.parse(
|
|
236
|
+
readFileSync(
|
|
237
|
+
resolve(__dirname, `../layers/dms-ui/i18n/locales/ui-${name}.json`),
|
|
238
|
+
"utf8",
|
|
239
|
+
),
|
|
240
|
+
).dms.live;
|
|
241
|
+
expect(locale("en-GB").direct).toBe("Live");
|
|
242
|
+
expect(locale("fr-FR").direct).toBe("Direct");
|
|
243
|
+
for (const key of ["paused", "resume", "reason_hidden", "polling"]) {
|
|
244
|
+
expect(locale("en-GB")[key]).toBeTruthy();
|
|
245
|
+
expect(locale("fr-FR")[key]).toBeTruthy();
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
});
|
|
@@ -38,6 +38,7 @@ it("loads a page registered after setup without changing the route", async () =>
|
|
|
38
38
|
await nextTick();
|
|
39
39
|
expect(loadPageLayout).toHaveBeenCalledExactlyOnceWith(
|
|
40
40
|
"/new-page/pagelayout",
|
|
41
|
+
{},
|
|
41
42
|
);
|
|
42
43
|
expect(pageLayout.value).toBe("/new-page/pagelayout");
|
|
43
44
|
expect(route.path).toBe("/new-page");
|