Skip to content

Commit 46d5e33

Browse files
authored
Tests: Enhance Vitest setup and add PLightbox component tests (photoprism#5320)
1 parent e8a97b0 commit 46d5e33

File tree

2 files changed

+141
-1
lines changed

2 files changed

+141
-1
lines changed
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { mount, config as VTUConfig } from "@vue/test-utils";
2+
import { describe, it, expect, beforeEach } from "vitest";
3+
import { nextTick } from "vue";
4+
import PLightbox from "component/lightbox.vue";
5+
6+
const mountLightbox = () =>
7+
mount(PLightbox, {
8+
global: {
9+
stubs: {
10+
"v-dialog": true,
11+
"v-icon": true,
12+
"v-slider": true,
13+
"p-lightbox-menu": true,
14+
"p-sidebar-info": true,
15+
},
16+
},
17+
});
18+
19+
describe("PLightbox (low-mock, jsdom-friendly)", () => {
20+
beforeEach(() => {
21+
localStorage.removeItem("lightbox.info");
22+
sessionStorage.removeItem("lightbox.muted");
23+
});
24+
25+
it("toggleInfo updates info and localStorage when visible", async () => {
26+
const wrapper = mountLightbox();
27+
await wrapper.setData({ visible: true });
28+
29+
// Use exposed onShortCut to trigger info toggle (KeyI)
30+
await wrapper.vm.onShortCut({ code: "KeyI" });
31+
await nextTick();
32+
expect(localStorage.getItem("lightbox.info")).toBe("true");
33+
34+
await wrapper.vm.onShortCut({ code: "KeyI" });
35+
await nextTick();
36+
expect(localStorage.getItem("lightbox.info")).toBe("false");
37+
});
38+
39+
it("toggleMute writes sessionStorage without requiring video or exposed state", async () => {
40+
const wrapper = mountLightbox();
41+
expect(sessionStorage.getItem("lightbox.muted")).toBeNull();
42+
await wrapper.vm.onShortCut({ code: "KeyM" });
43+
expect(sessionStorage.getItem("lightbox.muted")).toBe("true");
44+
await wrapper.vm.onShortCut({ code: "KeyM" });
45+
expect(sessionStorage.getItem("lightbox.muted")).toBe("false");
46+
});
47+
48+
it("getPadding returns expected structure for large and small screens", async () => {
49+
const wrapper = mountLightbox();
50+
// Large viewport
51+
const large = wrapper.vm.$options.methods.getPadding.call(
52+
wrapper.vm,
53+
{ x: 1200, y: 800 },
54+
{ width: 4000, height: 3000 }
55+
);
56+
expect(large).toHaveProperty("top");
57+
expect(large).toHaveProperty("bottom");
58+
expect(large).toHaveProperty("left");
59+
expect(large).toHaveProperty("right");
60+
61+
// Small viewport (<= mobileBreakpoint) should yield zeros
62+
const small = wrapper.vm.$options.methods.getPadding.call(
63+
wrapper.vm,
64+
{ x: 360, y: 640 },
65+
{ width: 1200, height: 800 }
66+
);
67+
expect(small).toEqual({ top: 0, bottom: 0, left: 0, right: 0 });
68+
});
69+
70+
it("KeyI is ignored when dialog is not visible", async () => {
71+
const wrapper = mountLightbox();
72+
expect(localStorage.getItem("lightbox.info")).toBeNull();
73+
await wrapper.vm.onShortCut({ code: "KeyI" });
74+
expect(localStorage.getItem("lightbox.info")).toBeNull();
75+
});
76+
77+
it("getViewport falls back to window size without content ref", () => {
78+
const wrapper = mountLightbox();
79+
const vp = wrapper.vm.$options.methods.getViewport.call(wrapper.vm);
80+
expect(vp.x).toBeGreaterThan(0);
81+
expect(vp.y).toBeGreaterThan(0);
82+
});
83+
84+
it("menuActions marks Download action visible when allowed", () => {
85+
const wrapper = mountLightbox();
86+
const ctx = {
87+
$gettext: VTUConfig.global.mocks.$gettext,
88+
$pgettext: VTUConfig.global.mocks.$pgettext,
89+
// minimal state needed by menuActions visibility checks
90+
canManageAlbums: false,
91+
canArchive: false,
92+
canDownload: true,
93+
collection: null,
94+
context: "",
95+
model: {},
96+
};
97+
const actions = wrapper.vm.$options.methods.menuActions.call(ctx);
98+
const download = actions.find((a) => a?.name === "download");
99+
expect(download).toBeTruthy();
100+
expect(download.visible).toBe(true);
101+
});
102+
});

frontend/tests/vitest/setup.js

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,48 @@ const vuetify = createVuetify({
2626
// Configure Vue Test Utils global configuration
2727
config.global.mocks = {
2828
$gettext: (text) => text,
29+
$pgettext: (_ctx, text) => text,
2930
$isRtl: false,
3031
$config: {
31-
feature: (_name) => true,
32+
feature: () => true,
33+
get: () => false,
34+
getSettings: () => ({ features: { edit: true, favorites: true, download: true, archive: true } }),
35+
allow: () => true,
36+
featExperimental: () => false,
37+
featDevelop: () => false,
38+
values: {},
39+
dir: () => "ltr",
3240
},
41+
$event: {
42+
subscribe: () => "sub-id",
43+
subscribeOnce: () => "sub-id-once",
44+
unsubscribe: () => {},
45+
publish: () => {},
46+
},
47+
$view: {
48+
enter: () => {},
49+
leave: () => {},
50+
isActive: () => true,
51+
},
52+
$notify: { success: () => {}, error: () => {}, warn: () => {} },
53+
$fullscreen: {
54+
isSupported: () => true,
55+
isEnabled: () => false,
56+
request: () => Promise.resolve(),
57+
exit: () => Promise.resolve(),
58+
},
59+
$clipboard: { selection: [], has: () => false, toggle: () => {} },
60+
$util: {
61+
hasTouch: () => false,
62+
encodeHTML: (s) => s,
63+
sanitizeHtml: (s) => s,
64+
formatSeconds: (n) => String(n),
65+
formatRemainingSeconds: () => "0",
66+
videoFormat: () => "avc",
67+
videoFormatUrl: () => "/v.mp4",
68+
thumb: () => ({ src: "/t.jpg", w: 100, h: 100 }),
69+
},
70+
$api: { post: vi.fn(), delete: vi.fn(), get: vi.fn() },
3371
};
3472

3573
config.global.plugins = [vuetify];

0 commit comments

Comments
 (0)