@antelopejs/dms 0.7.6 → 0.7.8
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 +35 -0
- package/dist/test/unit/interfaces/dms-base/column-display.test.js +15 -0
- package/dist/test/unit/interfaces/dms-base/column-display.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms-base/page-live.test.d.ts +1 -0
- package/dist/test/unit/interfaces/dms-base/page-live.test.js +66 -0
- package/dist/test/unit/interfaces/dms-base/page-live.test.js.map +1 -0
- package/dist/test/unit/interfaces/dms-base/table-view-source-reorder.test.js +16 -0
- package/dist/test/unit/interfaces/dms-base/table-view-source-reorder.test.js.map +1 -1
- package/frontend-vue/layers/dms-core/app/composables/period/usePeriod.ts +23 -3
- package/frontend-vue/layers/dms-core/i18n/locales/core-en-GB.json +2 -1
- package/frontend-vue/layers/dms-core/i18n/locales/core-fr-FR.json +2 -1
- package/frontend-vue/layers/dms-layout/app/build/components/layout/RowSidePanel.vue +134 -0
- package/frontend-vue/layers/dms-layout/app/build/composables/layout/rowSidePanel.ts +129 -0
- package/frontend-vue/layers/dms-layout/app/pages/[...slug].vue +6 -0
- package/frontend-vue/layers/dms-layout/app/types/page.ts +3 -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/form/FormSectionNav.vue +30 -15
- package/frontend-vue/layers/dms-ui/app/build/components/table/Table.vue +44 -8
- package/frontend-vue/layers/dms-ui/app/build/components/table-view/LiveHoldBanner.vue +35 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/actions/rowNavigation.ts +17 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/actions/useActionTargets.ts +107 -12
- package/frontend-vue/layers/dms-ui/app/build/composables/blocks/useRefreshTriggers.ts +26 -11
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/cellHelpers.ts +24 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/metricCells.ts +58 -16
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerDefaults.ts +30 -3
- package/frontend-vue/layers/dms-ui/app/build/composables/live/currentPageLive.ts +107 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/live/formatElapsed.ts +25 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/live/pageLiveController.ts +229 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/live/shortcuts.ts +8 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/live/useLiveToggleShortcut.ts +25 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/live/usePageLiveHost.ts +46 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/period/periodQuery.ts +330 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/period/usePeriodLocation.ts +189 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableListData.ts +15 -3
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableLiveHold.ts +77 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableRows.ts +53 -18
- 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/utils/recordLink.ts +61 -2
- package/frontend-vue/layers/dms-ui/app/build/utils/rowClickTrigger.ts +73 -0
- package/frontend-vue/layers/dms-ui/app/components/blocks/LiveToggleBlock.vue +92 -0
- package/frontend-vue/layers/dms-ui/app/components/field-row/FieldRow.vue +10 -4
- package/frontend-vue/layers/dms-ui/app/components/period/PeriodSelector.vue +19 -0
- package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +90 -12
- package/frontend-vue/layers/dms-ui/app/composables/form/types/field.ts +1 -1
- package/frontend-vue/layers/dms-ui/app/composables/live/usePageLive.ts +52 -0
- package/frontend-vue/layers/dms-ui/app/composables/table-view/shortcuts/index.ts +8 -0
- package/frontend-vue/layers/dms-ui/app/composables/table-view/shortcuts/tableViewRowPanel.ts +30 -0
- package/frontend-vue/layers/dms-ui/app/composables/table-view/types/action-target.ts +15 -0
- package/frontend-vue/layers/dms-ui/app/composables/table-view/types/config.ts +4 -0
- package/frontend-vue/layers/dms-ui/app/config/shortcuts-registry.ts +5 -0
- package/frontend-vue/layers/dms-ui/app/types/live.ts +8 -0
- package/frontend-vue/layers/dms-ui/app/types/row-action.ts +5 -2
- package/frontend-vue/layers/dms-ui/app/utils/blockRefresh.ts +44 -6
- package/frontend-vue/layers/dms-ui/i18n/locales/ui-en-GB.json +28 -0
- package/frontend-vue/layers/dms-ui/i18n/locales/ui-fr-FR.json +28 -0
- package/frontend-vue/tests/chart-live-pause.test.ts +74 -0
- package/frontend-vue/tests/field-row.test.ts +14 -0
- package/frontend-vue/tests/form-section-nav-groups.test.ts +75 -14
- package/frontend-vue/tests/live-hold-banner.test.ts +37 -0
- package/frontend-vue/tests/live-toggle.test.ts +162 -0
- package/frontend-vue/tests/page-live-gate.test.ts +241 -0
- package/frontend-vue/tests/period-live-pause.test.ts +120 -0
- package/frontend-vue/tests/period-selector-url.test.ts +268 -0
- package/frontend-vue/tests/period-url-query.test.ts +351 -0
- package/frontend-vue/tests/row-side-panel.test.ts +653 -0
- package/frontend-vue/tests/table-cell-tones.test.ts +176 -0
- package/frontend-vue/tests/table-live-hold.test.ts +204 -0
- package/frontend-vue/tests/table-source-period-scope.test.ts +144 -0
- package/frontend-vue/tests/table-view-refresh.test.ts +2 -2
- package/frontend-vue/tests/table-view-sources.test.ts +40 -0
- package/frontend-vue/tests/vue-inventory.test.ts +1 -1
- package/package.json +2 -2
- package/skills/dms/SKILL.md +11 -3
- package/skills/dms-pages/REFERENCE.md +5 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
/**
|
|
3
|
+
* A rolling period (`last-hour`, `last-24h`) slides every minute, and every
|
|
4
|
+
* block reading it refetches. On a paused live page the period holds still,
|
|
5
|
+
* so nothing refetches on the clock; a period the user picks still applies at
|
|
6
|
+
* once, and resuming catches the skipped minutes up in one read.
|
|
7
|
+
*/
|
|
8
|
+
import { readFileSync } from "node:fs";
|
|
9
|
+
import { resolve } from "node:path";
|
|
10
|
+
import { createApp, defineComponent, h, ref } from "vue";
|
|
11
|
+
import { afterEach, beforeEach, expect, it, vi } from "vitest";
|
|
12
|
+
import { createPageLiveController } from "../layers/dms-ui/app/build/composables/live/pageLiveController";
|
|
13
|
+
import { setCurrentPageLive } from "../layers/dms-ui/app/build/composables/live/currentPageLive";
|
|
14
|
+
import { usePageLive } from "../layers/dms-ui/app/composables/live/usePageLive";
|
|
15
|
+
import {
|
|
16
|
+
usePeriod,
|
|
17
|
+
type UsePeriodReturn,
|
|
18
|
+
} from "../layers/dms-core/app/composables/period/usePeriod";
|
|
19
|
+
import { registerPeriodScope } from "../layers/dms-core/app/composables/period/usePeriodScope";
|
|
20
|
+
|
|
21
|
+
const requests: string[] = [];
|
|
22
|
+
|
|
23
|
+
vi.stubGlobal("useAuthFetch", () => ({
|
|
24
|
+
$authFetch: async (url: string) => {
|
|
25
|
+
requests.push(url);
|
|
26
|
+
return { value: requests.length };
|
|
27
|
+
},
|
|
28
|
+
}));
|
|
29
|
+
vi.stubGlobal("useDmsRoute", () => ({ query: {} }));
|
|
30
|
+
vi.stubGlobal("ref", ref);
|
|
31
|
+
|
|
32
|
+
const { useChartFetch } = await import(
|
|
33
|
+
"../layers/dms-ui/app/composables/chart/useChartFetch"
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const SCOPE = "live-period";
|
|
37
|
+
const MINUTE_MS = 60_000;
|
|
38
|
+
const apps: Array<() => void> = [];
|
|
39
|
+
|
|
40
|
+
function mountPageWithChart(): UsePeriodReturn {
|
|
41
|
+
let period: UsePeriodReturn | undefined;
|
|
42
|
+
const app = createApp(
|
|
43
|
+
defineComponent({
|
|
44
|
+
setup() {
|
|
45
|
+
const { isPaused } = usePageLive();
|
|
46
|
+
// As the PeriodSelector wires it.
|
|
47
|
+
period = usePeriod({
|
|
48
|
+
defaultPreset: "last-hour",
|
|
49
|
+
holdClock: () => isPaused.value,
|
|
50
|
+
});
|
|
51
|
+
registerPeriodScope(SCOPE, period.state);
|
|
52
|
+
useChartFetch({ fetchUrl: "/api/chart", periodScope: SCOPE });
|
|
53
|
+
return () => h("div");
|
|
54
|
+
},
|
|
55
|
+
}),
|
|
56
|
+
);
|
|
57
|
+
app.mount(document.createElement("div"));
|
|
58
|
+
apps.push(() => app.unmount());
|
|
59
|
+
return period!;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
beforeEach(() => {
|
|
63
|
+
vi.useFakeTimers();
|
|
64
|
+
requests.length = 0;
|
|
65
|
+
window.localStorage.clear();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
afterEach(() => {
|
|
69
|
+
apps.splice(0).forEach((unmount) => unmount());
|
|
70
|
+
setCurrentPageLive(null);
|
|
71
|
+
vi.useRealTimers();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("slides and refetches every minute in Direct", async () => {
|
|
75
|
+
setCurrentPageLive(createPageLiveController("api.overview", {}));
|
|
76
|
+
mountPageWithChart();
|
|
77
|
+
await vi.advanceTimersByTimeAsync(1000);
|
|
78
|
+
expect(requests).toHaveLength(1);
|
|
79
|
+
|
|
80
|
+
await vi.advanceTimersByTimeAsync(2 * MINUTE_MS);
|
|
81
|
+
expect(requests).toHaveLength(3);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("holds the period while paused, but follows the user's pick", async () => {
|
|
85
|
+
const live = createPageLiveController("api.overview", {});
|
|
86
|
+
setCurrentPageLive(live);
|
|
87
|
+
const period = mountPageWithChart();
|
|
88
|
+
await vi.advanceTimersByTimeAsync(1000);
|
|
89
|
+
expect(requests).toHaveLength(1);
|
|
90
|
+
|
|
91
|
+
live.pause();
|
|
92
|
+
const heldKey = period.state.value.key;
|
|
93
|
+
await vi.advanceTimersByTimeAsync(3 * MINUTE_MS);
|
|
94
|
+
expect(requests).toHaveLength(1);
|
|
95
|
+
expect(period.state.value.key).toBe(heldKey);
|
|
96
|
+
|
|
97
|
+
period.setPreset("last-24h");
|
|
98
|
+
await vi.advanceTimersByTimeAsync(1000);
|
|
99
|
+
expect(requests).toHaveLength(2);
|
|
100
|
+
expect(requests[1]).toContain("preset=last-24h");
|
|
101
|
+
|
|
102
|
+
await vi.advanceTimersByTimeAsync(2 * MINUTE_MS);
|
|
103
|
+
expect(requests).toHaveLength(2);
|
|
104
|
+
|
|
105
|
+
live.resume();
|
|
106
|
+
await vi.advanceTimersByTimeAsync(1000);
|
|
107
|
+
expect(requests).toHaveLength(3);
|
|
108
|
+
expect(period.state.value.key).not.toBe(heldKey);
|
|
109
|
+
});
|
|
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
|
+
});
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
/**
|
|
3
|
+
* A PeriodSelector keeps its period in the page URL: the period it starts
|
|
4
|
+
* from (the URL, then the user's last choice, then its default), the server
|
|
5
|
+
* render (no flash of the default), the in-place write on a change (no new
|
|
6
|
+
* history entry), and the period following a route changed from outside.
|
|
7
|
+
*/
|
|
8
|
+
import {
|
|
9
|
+
createApp,
|
|
10
|
+
createSSRApp,
|
|
11
|
+
defineComponent,
|
|
12
|
+
h,
|
|
13
|
+
nextTick,
|
|
14
|
+
reactive,
|
|
15
|
+
type App,
|
|
16
|
+
} from "vue";
|
|
17
|
+
import { renderToString } from "vue/server-renderer";
|
|
18
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
19
|
+
import type { PeriodPreset } from "../layers/dms-core/app/composables/period/types";
|
|
20
|
+
|
|
21
|
+
const route = reactive<{ query: Record<string, string> }>({ query: {} });
|
|
22
|
+
const preferences = new Map<string, unknown>();
|
|
23
|
+
|
|
24
|
+
vi.stubGlobal("useDmsRoute", () => route);
|
|
25
|
+
// Inertia's client-side replace moves its page URL, which the DMS route
|
|
26
|
+
// follows: the route then holds the query the browser shows.
|
|
27
|
+
const { inertiaReplace } = vi.hoisted(() => ({
|
|
28
|
+
inertiaReplace: vi.fn(({ url }: { url: string }) => {
|
|
29
|
+
route.query = Object.fromEntries(
|
|
30
|
+
new URL(url, "http://dms.local").searchParams,
|
|
31
|
+
);
|
|
32
|
+
}),
|
|
33
|
+
}));
|
|
34
|
+
vi.mock("@inertiajs/vue3", () => ({ router: { replace: inertiaReplace } }));
|
|
35
|
+
vi.stubGlobal("usePreferences", () => ({
|
|
36
|
+
getPreference: <T>(key: string, fallback?: T) =>
|
|
37
|
+
(preferences.get(key) as T | undefined) ?? fallback,
|
|
38
|
+
setPreference: (key: string, value: unknown) => preferences.set(key, value),
|
|
39
|
+
}));
|
|
40
|
+
|
|
41
|
+
const { usePeriod } = await import(
|
|
42
|
+
"../layers/dms-core/app/composables/period/usePeriod"
|
|
43
|
+
);
|
|
44
|
+
const { usePeriodLocation, PERIOD_PREFERENCE_KEY } = await import(
|
|
45
|
+
"../layers/dms-ui/app/build/composables/period/usePeriodLocation"
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
type Period = ReturnType<typeof usePeriod>;
|
|
49
|
+
|
|
50
|
+
const PAGE_ID = "dashboard";
|
|
51
|
+
const PAGE_PATH = "/dashboard";
|
|
52
|
+
const handles: Record<string, Period> = {};
|
|
53
|
+
|
|
54
|
+
/** The selector's state wiring, as PeriodSelector.vue does it in setup. */
|
|
55
|
+
function selector(id: string, defaultPreset: PeriodPreset = "this-month") {
|
|
56
|
+
return defineComponent({
|
|
57
|
+
setup() {
|
|
58
|
+
const period = usePeriod({
|
|
59
|
+
defaultPreset,
|
|
60
|
+
presets: ["last-7-days", "last-30-days", "this-month", "custom"],
|
|
61
|
+
});
|
|
62
|
+
usePeriodLocation(period, {
|
|
63
|
+
id,
|
|
64
|
+
pageId: PAGE_ID,
|
|
65
|
+
fallback: {
|
|
66
|
+
preset: period.preset.value,
|
|
67
|
+
comparison: period.comparison.value,
|
|
68
|
+
customRange: null,
|
|
69
|
+
customCompareRange: null,
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
handles[id] = period;
|
|
73
|
+
return () =>
|
|
74
|
+
h("span", `${period.preset.value}|${period.comparison.value}`);
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const mounted: App[] = [];
|
|
80
|
+
|
|
81
|
+
function mount(...ids: string[]): void {
|
|
82
|
+
const app = createApp({
|
|
83
|
+
setup: () => () =>
|
|
84
|
+
h(
|
|
85
|
+
"div",
|
|
86
|
+
ids.map((id) => h(selector(id))),
|
|
87
|
+
),
|
|
88
|
+
});
|
|
89
|
+
app.mount(document.createElement("div"));
|
|
90
|
+
mounted.push(app);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const search = () => new URLSearchParams(window.location.search);
|
|
94
|
+
|
|
95
|
+
const remember = (query: Record<string, string>) =>
|
|
96
|
+
preferences.set(PERIOD_PREFERENCE_KEY, { [`${PAGE_ID}#page`]: query });
|
|
97
|
+
|
|
98
|
+
beforeEach(() => {
|
|
99
|
+
route.query = {};
|
|
100
|
+
preferences.clear();
|
|
101
|
+
inertiaReplace.mockClear();
|
|
102
|
+
window.history.replaceState(null, "", PAGE_PATH);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
afterEach(() => {
|
|
106
|
+
mounted.splice(0).forEach((app) => app.unmount());
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe("the period a selector starts from", () => {
|
|
110
|
+
it("is the URL's", () => {
|
|
111
|
+
route.query = {
|
|
112
|
+
"page.period": "last-7-days",
|
|
113
|
+
"page.compare": "previous-year",
|
|
114
|
+
};
|
|
115
|
+
remember({
|
|
116
|
+
period: "last-30-days",
|
|
117
|
+
});
|
|
118
|
+
mount("page");
|
|
119
|
+
expect(handles.page!.preset.value).toBe("last-7-days");
|
|
120
|
+
expect(handles.page!.comparison.value).toBe("previous-year");
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("is the user's last choice when the URL names none", () => {
|
|
124
|
+
remember({
|
|
125
|
+
period: "last-30-days",
|
|
126
|
+
compare: "previous-period",
|
|
127
|
+
});
|
|
128
|
+
mount("page");
|
|
129
|
+
expect(handles.page!.preset.value).toBe("last-30-days");
|
|
130
|
+
expect(handles.page!.comparison.value).toBe("previous-period");
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("is the default when neither names a valid one", () => {
|
|
134
|
+
route.query = { "page.period": "next-century" };
|
|
135
|
+
remember({
|
|
136
|
+
period: "ytd",
|
|
137
|
+
});
|
|
138
|
+
mount("page");
|
|
139
|
+
expect(handles.page!.preset.value).toBe("this-month");
|
|
140
|
+
expect(handles.page!.comparison.value).toBe("none");
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("is a custom range read from the URL", () => {
|
|
144
|
+
route.query = { "page.from": "2026-10-01", "page.to": "2026-10-15" };
|
|
145
|
+
mount("page");
|
|
146
|
+
expect(handles.page!.preset.value).toBe("custom");
|
|
147
|
+
expect(handles.page!.state.value.range).toEqual({
|
|
148
|
+
from: new Date(2026, 9, 1),
|
|
149
|
+
to: new Date(2026, 9, 15, 23, 59, 59, 999),
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("is rendered by the server, so the default never flashes", async () => {
|
|
154
|
+
route.query = { "page.period": "last-7-days" };
|
|
155
|
+
const html = await renderToString(createSSRApp(selector("page")));
|
|
156
|
+
expect(html).toContain("last-7-days|none");
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
describe("the URL on load", () => {
|
|
161
|
+
it("carries the period the selector starts from, the default included", () => {
|
|
162
|
+
window.history.replaceState(null, "", `${PAGE_PATH}?requests.tab=get`);
|
|
163
|
+
mount("page");
|
|
164
|
+
expect(Object.fromEntries(search())).toEqual({
|
|
165
|
+
"requests.tab": "get",
|
|
166
|
+
"page.period": "this-month",
|
|
167
|
+
"page.compare": "none",
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("is left alone when it already names the period", () => {
|
|
172
|
+
window.history.replaceState(
|
|
173
|
+
null,
|
|
174
|
+
"",
|
|
175
|
+
`${PAGE_PATH}?page.period=this-month&page.compare=none`,
|
|
176
|
+
);
|
|
177
|
+
route.query = { "page.period": "this-month", "page.compare": "none" };
|
|
178
|
+
mount("page");
|
|
179
|
+
expect(inertiaReplace).not.toHaveBeenCalled();
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
describe("a change of period", () => {
|
|
184
|
+
it("reaches the route, so a tab written from it keeps the new period", async () => {
|
|
185
|
+
mount("page");
|
|
186
|
+
handles.page!.setPreset("last-7-days");
|
|
187
|
+
await nextTick();
|
|
188
|
+
// What useTab does: the route's query, plus its own key.
|
|
189
|
+
const query = { ...route.query, tab: "2" };
|
|
190
|
+
window.history.replaceState(
|
|
191
|
+
null,
|
|
192
|
+
"",
|
|
193
|
+
`${PAGE_PATH}?${new URLSearchParams(query)}`,
|
|
194
|
+
);
|
|
195
|
+
expect(search().get("tab")).toBe("2");
|
|
196
|
+
expect(search().get("page.period")).toBe("last-7-days");
|
|
197
|
+
expect(handles.page!.preset.value).toBe("last-7-days");
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("replaces the URL in place and remembers the choice", async () => {
|
|
201
|
+
mount("page");
|
|
202
|
+
const entries = window.history.length;
|
|
203
|
+
handles.page!.setPreset("last-7-days");
|
|
204
|
+
handles.page!.setComparison("previous-period");
|
|
205
|
+
await nextTick();
|
|
206
|
+
expect(search().get("page.period")).toBe("last-7-days");
|
|
207
|
+
expect(search().get("page.compare")).toBe("previous-period");
|
|
208
|
+
expect(window.history.length).toBe(entries);
|
|
209
|
+
expect(
|
|
210
|
+
(preferences.get(PERIOD_PREFERENCE_KEY) as Record<string, unknown>)[
|
|
211
|
+
`${PAGE_ID}#page`
|
|
212
|
+
],
|
|
213
|
+
).toEqual({ period: "last-7-days", compare: "previous-period" });
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it("swaps the preset key for the custom range keys", async () => {
|
|
217
|
+
window.history.replaceState(
|
|
218
|
+
null,
|
|
219
|
+
"",
|
|
220
|
+
`${PAGE_PATH}?page.period=last-7-days`,
|
|
221
|
+
);
|
|
222
|
+
mount("page");
|
|
223
|
+
handles.page!.setCustomRange({
|
|
224
|
+
from: new Date(2026, 9, 1),
|
|
225
|
+
to: new Date(2026, 9, 2, 23, 59, 59, 999),
|
|
226
|
+
});
|
|
227
|
+
await nextTick();
|
|
228
|
+
expect(search().get("page.period")).toBeNull();
|
|
229
|
+
expect(search().get("page.from")).toBe("2026-10-01");
|
|
230
|
+
expect(search().get("page.to")).toBe("2026-10-02");
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it("leaves another selector's keys and the other parameters alone", async () => {
|
|
234
|
+
window.history.replaceState(null, "", `${PAGE_PATH}?requests.tab=get`);
|
|
235
|
+
mount("sales", "traffic");
|
|
236
|
+
handles.sales!.setPreset("last-7-days");
|
|
237
|
+
await nextTick();
|
|
238
|
+
handles.traffic!.setPreset("last-30-days");
|
|
239
|
+
await nextTick();
|
|
240
|
+
expect(Object.fromEntries(search())).toEqual({
|
|
241
|
+
"requests.tab": "get",
|
|
242
|
+
"sales.period": "last-7-days",
|
|
243
|
+
"sales.compare": "none",
|
|
244
|
+
"traffic.period": "last-30-days",
|
|
245
|
+
"traffic.compare": "none",
|
|
246
|
+
});
|
|
247
|
+
expect(handles.sales!.preset.value).toBe("last-7-days");
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
describe("a route changed from outside", () => {
|
|
252
|
+
it("moves the period of the selector it names", async () => {
|
|
253
|
+
mount("sales", "traffic");
|
|
254
|
+
route.query = { "sales.period": "last-30-days" };
|
|
255
|
+
await nextTick();
|
|
256
|
+
expect(handles.sales!.preset.value).toBe("last-30-days");
|
|
257
|
+
expect(handles.traffic!.preset.value).toBe("this-month");
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it("keeps the period when it names an invalid one", async () => {
|
|
261
|
+
mount("page");
|
|
262
|
+
handles.page!.setPreset("last-7-days");
|
|
263
|
+
await nextTick();
|
|
264
|
+
route.query = { "page.period": "last-decade" };
|
|
265
|
+
await nextTick();
|
|
266
|
+
expect(handles.page!.preset.value).toBe("last-7-days");
|
|
267
|
+
});
|
|
268
|
+
});
|