mirror of
https://github.com/photoprism/photoprism.git
synced 2026-07-17 16:49:04 +00:00
This commit is contained in:
parent
408c6fc6aa
commit
ebf872d5dd
7 changed files with 93 additions and 94 deletions
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
- Follow the lint/format scripts in `frontend/package.json`; all added JS, Vue, and tests must conform.
|
||||
- Unit tests (Vitest): `make test-js`, `make vitest-watch`, `make vitest-coverage`. Acceptance: `acceptance-*` targets in the root `Makefile`.
|
||||
- **Always invoke Vitest through the npm/make wrapper, never bare `npx vitest run`.** `frontend/package.json`'s `test` script wraps the call in `cross-env TZ=UTC BUILD_ENV=development NODE_ENV=development BABEL_ENV=test`. Without those env vars ~50 component tests (Vuetify renders, chip-selector, login, location-input, batch-edit, people-tab, lightbox `toggleInfo`, etc.) and TZ-sensitive date tests fail spuriously — the failures look real but only reproduce in the unwrapped invocation. Do not compare a "failed N, passed M" report from bare `npx vitest run` against a `make test-js` baseline. For ad-hoc filtering on a single file, mirror the env explicitly: `(cd frontend && TZ=UTC BUILD_ENV=development NODE_ENV=development BABEL_ENV=test npx vitest run <path>)`.
|
||||
- **Always invoke Vitest through the npm/make wrapper, never bare `npx vitest run`.** `frontend/package.json`'s `test` script wraps the call in `cross-env TZ=UTC BUILD_ENV=development NODE_ENV=development BABEL_ENV=test`. Without those env vars ~50 component tests (Vuetify renders, chip-selector, login, location-input, batch-edit, people-tab, lightbox `toggleSidebar`, etc.) and TZ-sensitive date tests fail spuriously — the failures look real but only reproduce in the unwrapped invocation. Do not compare a "failed N, passed M" report from bare `npx vitest run` against a `make test-js` baseline. For ad-hoc filtering on a single file, mirror the env explicitly: `(cd frontend && TZ=UTC BUILD_ENV=development NODE_ENV=development BABEL_ENV=test npx vitest run <path>)`.
|
||||
- One-off TestCafe (single case by `testID`):
|
||||
```bash
|
||||
make storage/acceptance
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@
|
|||
class="p-lightbox__content no-transition"
|
||||
:class="{
|
||||
'hide-caption': hideCaption,
|
||||
'sidebar-visible': info,
|
||||
'sidebar-visible': sidebarVisible,
|
||||
'face-marker-mode': faceMarkers.active,
|
||||
'slideshow-active': slideshow.active,
|
||||
'is-fullscreen': isFullscreen(),
|
||||
|
|
@ -90,11 +90,11 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="info" ref="sidebar" tabindex="-1" class="p-lightbox__sidebar bg-background">
|
||||
<div v-if="sidebarVisible" tabindex="-1" class="p-lightbox__sidebar bg-background">
|
||||
<p-lightbox-sidebar
|
||||
ref="sidebarInfo"
|
||||
ref="sidebar"
|
||||
:uid="model.UID"
|
||||
@close="hideInfo"
|
||||
@close="hideSidebar"
|
||||
@toggle-face-marker-mode="toggleFaceMarkerMode"
|
||||
@toggle-face-marker-edit="toggleFaceMarkerEdit"
|
||||
@eject-marker="onEjectFaceMarker"
|
||||
|
|
@ -159,12 +159,12 @@ const appStorage = getAppStorage();
|
|||
const appSessionStorage = getAppSessionStorage();
|
||||
const viewportPadding = { top: 0, bottom: 0, left: 0, right: 0 };
|
||||
|
||||
// shouldShowInfo returns the persisted sidebar-visible flag.
|
||||
const shouldShowInfo = () => {
|
||||
return appStorage.getItem("lightbox.info") === "true";
|
||||
// shouldShowSidebar returns the persisted sidebar visibility flag.
|
||||
const shouldShowSidebar = () => {
|
||||
return appStorage.getItem("lightbox.sidebar") === "true";
|
||||
};
|
||||
|
||||
// shouldHideCaption returns the persisted Ctrl+H caption-hidden flag;
|
||||
// shouldHideCaption returns the persisted Ctrl+H caption visibility flag;
|
||||
// a missing key resolves to visible so first-time users see the caption.
|
||||
const shouldHideCaption = () => {
|
||||
return appStorage.getItem("lightbox.caption") === "false";
|
||||
|
|
@ -182,11 +182,11 @@ export default {
|
|||
return {
|
||||
debug,
|
||||
trace,
|
||||
visible: false,
|
||||
busy: false,
|
||||
closing: false,
|
||||
info: shouldShowInfo(),
|
||||
hideCaption: shouldHideCaption() || shouldShowInfo(),
|
||||
visible: false,
|
||||
sidebarVisible: shouldShowSidebar(),
|
||||
hideCaption: shouldHideCaption() || shouldShowSidebar(),
|
||||
menuElement: null,
|
||||
menuBgColor: "#252525",
|
||||
menuVisible: false,
|
||||
|
|
@ -351,8 +351,8 @@ export default {
|
|||
this.closing = false;
|
||||
this.visible = true;
|
||||
this.wasFullscreen = $fullscreen.isEnabled();
|
||||
this.info = shouldShowInfo();
|
||||
this.hideCaption = shouldHideCaption() || this.info;
|
||||
this.sidebarVisible = shouldShowSidebar();
|
||||
this.hideCaption = shouldHideCaption() || this.sidebarVisible;
|
||||
|
||||
// Publish init event.
|
||||
this.$event.publish("lightbox.init");
|
||||
|
|
@ -364,7 +364,7 @@ export default {
|
|||
this.resetFaceMarkers();
|
||||
|
||||
// Hide sidebar.
|
||||
this.info = false;
|
||||
this.sidebarVisible = false;
|
||||
|
||||
// Remove lightbox focus and hide lightbox.
|
||||
if (this.visible) {
|
||||
|
|
@ -420,10 +420,10 @@ export default {
|
|||
|
||||
return this.$refs.lightbox;
|
||||
},
|
||||
// Returns the metadata sidebar element.
|
||||
getSidebarElement() {
|
||||
// Returns the sidebar Vue component proxy.
|
||||
getSidebar() {
|
||||
if (!this.$refs.sidebar) {
|
||||
this.log("sidebar element is not visible");
|
||||
this.log("sidebar component is not visible");
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -1309,7 +1309,7 @@ export default {
|
|||
}),
|
||||
});
|
||||
|
||||
// Add information toggle button.
|
||||
// Add sidebar toggle control.
|
||||
if (window.innerWidth > this.mobileBreakpoint) {
|
||||
lightbox.pswp.ui.registerElement({
|
||||
name: "sidebar-button",
|
||||
|
|
@ -1325,7 +1325,7 @@ export default {
|
|||
outlineID: "pswp__icn-info", // Add this to the <path> in the inner property.
|
||||
size: 24, // Depends on the original SVG viewBox, e.g. use 24 for viewBox="0 0 24 24".
|
||||
},
|
||||
onClick: (ev) => this.onControlClick(ev, this.toggleInfo),
|
||||
onClick: (ev) => this.onControlClick(ev, this.toggleSidebar),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -1529,7 +1529,7 @@ export default {
|
|||
icon: "mdi-text-box-outline",
|
||||
text: this.$gettext("Show Caption"),
|
||||
shortcut: "Ctrl-H",
|
||||
visible: !this.info && this.hideCaption,
|
||||
visible: !this.sidebarVisible && this.hideCaption,
|
||||
click: () => {
|
||||
this.toggleCaption();
|
||||
this.$refs.menu?.hide();
|
||||
|
|
@ -1540,7 +1540,7 @@ export default {
|
|||
icon: "mdi-text-box-remove-outline",
|
||||
text: this.$gettext("Hide Caption"),
|
||||
shortcut: "Ctrl-H",
|
||||
visible: !this.info && !this.hideCaption,
|
||||
visible: !this.sidebarVisible && !this.hideCaption,
|
||||
click: () => {
|
||||
this.toggleCaption();
|
||||
this.$refs.menu?.hide();
|
||||
|
|
@ -1747,8 +1747,8 @@ export default {
|
|||
// still sees the dirty old photo. On cancel, revert via pswp.goTo().
|
||||
if (this._suppressNavCheck) {
|
||||
this._suppressNavCheck = false;
|
||||
} else if (newIndex !== oldIndex && this.info && newIndex >= 0 && oldIndex >= 0) {
|
||||
const sidebar = this.$refs.sidebarInfo;
|
||||
} else if (newIndex !== oldIndex && this.sidebarVisible && newIndex >= 0 && oldIndex >= 0) {
|
||||
const sidebar = this.getSidebar();
|
||||
if (sidebar && typeof sidebar.hasPendingEdit === "function" && sidebar.hasPendingEdit()) {
|
||||
const rollbackIndex = oldIndex;
|
||||
this.$nextTick(() => {
|
||||
|
|
@ -1784,7 +1784,7 @@ export default {
|
|||
}
|
||||
|
||||
// Fetch full photo metadata for the sidebar if it is visible.
|
||||
if (this.info) {
|
||||
if (this.sidebarVisible) {
|
||||
this.fetchPhoto(this.model.UID);
|
||||
this.preloadNextPhoto();
|
||||
}
|
||||
|
|
@ -1834,7 +1834,7 @@ export default {
|
|||
this.$nextTick(() => overlay.scheduleUpdate());
|
||||
}
|
||||
},
|
||||
// Fully exits face-marker UI. Eye-toggle / Escape / hideInfo all
|
||||
// Fully exits face-marker UI. Eye-toggle / Escape / hideSidebar all
|
||||
// route through here.
|
||||
exitFaceMarkerMode() {
|
||||
this.faceMarkers.exit();
|
||||
|
|
@ -1875,10 +1875,10 @@ export default {
|
|||
},
|
||||
// Asks the sidebar (if mounted) whether it has unsaved edits, returning
|
||||
// a Promise that resolves true to proceed and false to cancel. Used by
|
||||
// every gesture that would tear the sidebar down (hideInfo, slide nav,
|
||||
// every gesture that would tear the sidebar down (hideSidebar, slide nav,
|
||||
// close) so the user is prompted before their in-flight changes disappear.
|
||||
confirmDiscardSidebar() {
|
||||
const sidebar = this.$refs.sidebarInfo;
|
||||
const sidebar = this.getSidebar();
|
||||
if (sidebar && typeof sidebar.confirmDiscardPending === "function") {
|
||||
return Promise.resolve(sidebar.confirmDiscardPending());
|
||||
}
|
||||
|
|
@ -2037,7 +2037,7 @@ export default {
|
|||
// so sessions without library access don't issue prefetch GETs for
|
||||
// long-form sidebar fields they aren't allowed to see anyway.
|
||||
preloadNextPhoto() {
|
||||
if (!this.info || !this.models.length || this.$config.deny("photos", "access_library")) {
|
||||
if (!this.sidebarVisible || !this.models.length || this.$config.deny("photos", "access_library")) {
|
||||
return;
|
||||
}
|
||||
Photo.prefetchAround(this.models, this.index, { before: 0, after: 1 });
|
||||
|
|
@ -2477,7 +2477,7 @@ export default {
|
|||
this.toggleCaption();
|
||||
return true;
|
||||
case "KeyI":
|
||||
this.toggleInfo();
|
||||
this.toggleSidebar();
|
||||
return true;
|
||||
case "KeyL":
|
||||
this.onShowMenu();
|
||||
|
|
@ -2499,7 +2499,7 @@ export default {
|
|||
return;
|
||||
}
|
||||
|
||||
if (this.info && (document.activeElement instanceof HTMLInputElement || document.activeElement instanceof HTMLTextAreaElement)) {
|
||||
if (this.sidebarVisible && (document.activeElement instanceof HTMLInputElement || document.activeElement instanceof HTMLTextAreaElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -2601,7 +2601,7 @@ export default {
|
|||
// typed into sidebar inputs. `preventDefault` makes `_onKeyDown`
|
||||
// bail before its switch statement.
|
||||
onPswpKeyDown(ev) {
|
||||
if (!ev || !this.info) {
|
||||
if (!ev || !this.sidebarVisible) {
|
||||
return;
|
||||
}
|
||||
const active = document.activeElement;
|
||||
|
|
@ -2709,7 +2709,7 @@ export default {
|
|||
// reads the flushed `.hide-caption` class — see H8 in best-practices.
|
||||
// No-op when the lightbox is hidden or the sidebar is open.
|
||||
toggleCaption() {
|
||||
if (!this.visible || this.info) {
|
||||
if (!this.visible || this.sidebarVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -2982,28 +2982,28 @@ export default {
|
|||
}
|
||||
}
|
||||
},
|
||||
toggleInfo() {
|
||||
toggleSidebar() {
|
||||
if (!this.visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.info) {
|
||||
this.hideInfo();
|
||||
if (this.sidebarVisible) {
|
||||
this.hideSidebar();
|
||||
} else {
|
||||
this.showInfo();
|
||||
this.showSidebar();
|
||||
}
|
||||
},
|
||||
// Shows the lightbox sidebar, if hidden.
|
||||
showInfo() {
|
||||
if (!this.visible || this.info) {
|
||||
showSidebar() {
|
||||
if (!this.visible || this.sidebarVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.info = true;
|
||||
this.sidebarVisible = true;
|
||||
// Sidebar renders the caption itself; suppress the overlay so it
|
||||
// doesn't reserve viewport padding. hideInfo() restores the choice.
|
||||
// doesn't reserve viewport padding. hideSidebar() restores the choice.
|
||||
this.hideCaption = true;
|
||||
appStorage.setItem("lightbox.info", `${this.info.toString()}`);
|
||||
appStorage.setItem("lightbox.sidebar", `${this.sidebarVisible.toString()}`);
|
||||
|
||||
// Fetch full photo metadata when sidebar is opened.
|
||||
this.fetchPhoto(this.model?.UID);
|
||||
|
|
@ -3019,8 +3019,8 @@ export default {
|
|||
// UI when active — the eye and pencil controls live in the sidebar,
|
||||
// so a closed sidebar would otherwise leave the overlay mounted with
|
||||
// no UI to disable it (see P1-10).
|
||||
async hideInfo() {
|
||||
if (!this.visible || !this.info) {
|
||||
async hideSidebar() {
|
||||
if (!this.visible || !this.sidebarVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -3029,14 +3029,14 @@ export default {
|
|||
return;
|
||||
}
|
||||
|
||||
this.info = false;
|
||||
this.sidebarVisible = false;
|
||||
// Restore the user's persisted Ctrl+H caption preference (#5580).
|
||||
this.hideCaption = shouldHideCaption();
|
||||
if (this.faceMarkers.active) {
|
||||
this.exitFaceMarkerMode();
|
||||
}
|
||||
|
||||
appStorage.setItem("lightbox.info", `${this.info.toString()}`);
|
||||
appStorage.setItem("lightbox.sidebar", `${this.sidebarVisible.toString()}`);
|
||||
|
||||
// Resize and focus content element.
|
||||
this.$nextTick(() => {
|
||||
|
|
|
|||
|
|
@ -290,7 +290,7 @@ test.meta("testID", "settings-general-004").meta({ type: "short", mode: "auth" }
|
|||
// that rewrote the sidebar gated both on $config.feature("people"),
|
||||
// so this assertion fails if the feature flag no longer propagates.
|
||||
await photoviewer.openPhotoViewer("nth", 0);
|
||||
await photoviewer.openInfoSidebar();
|
||||
await photoviewer.openSidebar();
|
||||
await t.expect(Selector(".p-lightbox-sidebar .text-subtitle-2").withText("People").exists).ok();
|
||||
await t.expect(photoviewer.markersVisibilityToggle.exists).ok();
|
||||
await photoviewer.triggerPhotoViewerAction("close-button");
|
||||
|
|
@ -317,7 +317,7 @@ test.meta("testID", "settings-general-004").meta({ type: "short", mode: "auth" }
|
|||
// People section and the face-marker toggle/add controls, even for
|
||||
// an admin on a photo that has markers in the fixtures.
|
||||
await photoviewer.openPhotoViewer("nth", 0);
|
||||
await photoviewer.openInfoSidebar();
|
||||
await photoviewer.openSidebar();
|
||||
await t.expect(Selector(".p-lightbox-sidebar .text-subtitle-2").withText("People").exists).notOk();
|
||||
await t.expect(photoviewer.markersVisibilityToggle.exists).notOk();
|
||||
await t.expect(photoviewer.markerAddButton.exists).notOk();
|
||||
|
|
@ -458,7 +458,7 @@ test.meta("testID", "settings-general-006").meta({ type: "short", mode: "auth" }
|
|||
|
||||
// Baseline sidebar assertion: with Edit enabled, the lightbox
|
||||
// sidebar exposes pencil icons for inline editing.
|
||||
await photoviewer.openInfoSidebar();
|
||||
await photoviewer.openSidebar();
|
||||
await t.expect(photoviewer.inlinePencils.exists).ok();
|
||||
|
||||
await photoviewer.triggerPhotoViewerAction("close-button");
|
||||
|
|
@ -555,7 +555,7 @@ test.meta("testID", "settings-general-006").meta({ type: "short", mode: "auth" }
|
|||
// With Edit disabled, the sidebar must drop every inline edit
|
||||
// affordance: no pencil icons, no add-prompt placeholders, and no
|
||||
// active inline edit inputs.
|
||||
await photoviewer.openInfoSidebar();
|
||||
await photoviewer.openSidebar();
|
||||
await t.expect(photoviewer.inlinePencils.exists).notOk();
|
||||
await t.expect(photoviewer.inlineAddPrompt.exists).notOk();
|
||||
|
||||
|
|
|
|||
|
|
@ -207,7 +207,7 @@ test.meta("testID", "sharing-003").meta({ type: "short", mode: "auth" })("Common
|
|||
await t.expect(toolbar.toolbarSecondTitle.withText("British Columbia").visible).ok();
|
||||
|
||||
await photoviewer.openPhotoViewer("nth", 0);
|
||||
await photoviewer.openInfoSidebar();
|
||||
await photoviewer.openSidebar();
|
||||
|
||||
// Allow-list: file info and taken-at rows render for anonymous
|
||||
// viewers so they can tell what they are looking at.
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ async function openSidebarOnFirstPhoto(t) {
|
|||
await t.click(toolbar.cardsViewAction);
|
||||
const uid = await photo.getNthPhotoUid("image", 0);
|
||||
await photoviewer.openPhotoViewer("uid", uid);
|
||||
await photoviewer.openInfoSidebar();
|
||||
await photoviewer.openSidebar();
|
||||
return uid;
|
||||
}
|
||||
|
||||
|
|
@ -41,7 +41,7 @@ test.meta("testID", "face-markers-002").meta({ mode: "public" })(
|
|||
await t.click(toolbar.cardsViewAction);
|
||||
const uid = await photo.getNthPhotoUid("image", 0);
|
||||
await photoviewer.openPhotoViewer("uid", uid);
|
||||
await photoviewer.openInfoSidebar();
|
||||
await photoviewer.openSidebar();
|
||||
await t.expect(Selector("div.text-subtitle-2").withText("People").exists).ok();
|
||||
await t.expect(photoviewer.markersVisibilityToggle.exists).ok();
|
||||
await t.expect(photoviewer.markerAddButton.exists).ok();
|
||||
|
|
@ -114,7 +114,7 @@ test.meta("testID", "face-markers-006").meta({ mode: "public" })("Common: Named
|
|||
// named row we just assert the structural rule on the rendered DOM.
|
||||
const uid = await photo.getNthPhotoUid("image", 0);
|
||||
await photoviewer.openPhotoViewer("uid", uid);
|
||||
await photoviewer.openInfoSidebar();
|
||||
await photoviewer.openSidebar();
|
||||
|
||||
const namedRows = photoviewer.personRow.filter((node) => node.querySelector(".meta-marker-remove") === null);
|
||||
const count = await namedRows.count;
|
||||
|
|
|
|||
|
|
@ -19,9 +19,8 @@ export default class Page {
|
|||
this.playButton = Selector('[class^="pswp__button pswp__button--slideshow-toggle pswp__"]', { timeout: 5000 });
|
||||
this.favoriteOnIcon = Selector("button.action-favorite i.icon-favorite", { timeout: 5000 });
|
||||
this.favoriteOffIcon = Selector("button.action-favorite i.icon-favorite-border", { timeout: 5000 });
|
||||
// Sidebar info + face markers.
|
||||
this.sidebar = Selector("div.p-lightbox__sidebar", { timeout: 15000 });
|
||||
this.sidebarInfo = Selector("div.p-lightbox-sidebar", { timeout: 15000 });
|
||||
// Sidebar + face markers.
|
||||
this.sidebar = Selector("div.p-lightbox__sidebar div.p-lightbox-sidebar", { timeout: 15000 });
|
||||
this.markersVisibilityToggle = Selector(".meta-markers-toggle", { timeout: 15000 });
|
||||
this.markerAddButton = Selector(".meta-faces-edit", { timeout: 15000 });
|
||||
this.markerRemoveButton = Selector(".meta-marker-remove", { timeout: 5000 });
|
||||
|
|
@ -128,7 +127,7 @@ export default class Page {
|
|||
await t.click(toolbar.cardsViewAction);
|
||||
const uid = await photo.getNthPhotoUid("image", 0);
|
||||
await this.openPhotoViewer("uid", uid);
|
||||
await this.openInfoSidebar();
|
||||
await this.openSidebar();
|
||||
return uid;
|
||||
}
|
||||
|
||||
|
|
@ -149,7 +148,7 @@ export default class Page {
|
|||
}
|
||||
}
|
||||
|
||||
async openInfoSidebar() {
|
||||
async openSidebar() {
|
||||
if (!(await this.sidebar.exists)) {
|
||||
await t.click(Selector("button.pswp__button--info-button"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ const makeFaceMarkers = (overrides = {}) => ({
|
|||
});
|
||||
|
||||
const storagePrefix = buildNamespace(clientConfig.storageNamespace);
|
||||
const infoKey = `${storagePrefix}lightbox.info`;
|
||||
const sidebarKey = `${storagePrefix}lightbox.sidebar`;
|
||||
const mutedKey = `${storagePrefix}lightbox.muted`;
|
||||
const captionKey = `${storagePrefix}lightbox.caption`;
|
||||
|
||||
|
|
@ -66,23 +66,23 @@ const allowAllConfig = { allow: () => true, deny: () => false };
|
|||
|
||||
describe("PLightbox (low-mock, jsdom-friendly)", () => {
|
||||
beforeEach(() => {
|
||||
localStorage.removeItem(infoKey);
|
||||
localStorage.removeItem(sidebarKey);
|
||||
localStorage.removeItem(captionKey);
|
||||
sessionStorage.removeItem(mutedKey);
|
||||
});
|
||||
|
||||
it("toggleInfo updates info and localStorage when visible", async () => {
|
||||
it("toggleSidebar updates sidebar and localStorage when visible", async () => {
|
||||
const wrapper = mountLightbox();
|
||||
await wrapper.setData({ visible: true });
|
||||
|
||||
// Use exposed onShortCut to trigger info toggle (KeyI)
|
||||
// Use exposed onShortCut to trigger sidebar toggle (KeyI)
|
||||
await wrapper.vm.onShortCut({ code: "KeyI" });
|
||||
await nextTick();
|
||||
expect(localStorage.getItem(infoKey)).toBe("true");
|
||||
expect(localStorage.getItem(sidebarKey)).toBe("true");
|
||||
|
||||
await wrapper.vm.onShortCut({ code: "KeyI" });
|
||||
await nextTick();
|
||||
expect(localStorage.getItem(infoKey)).toBe("false");
|
||||
expect(localStorage.getItem(sidebarKey)).toBe("false");
|
||||
});
|
||||
|
||||
it("toggleMute writes sessionStorage without requiring video or exposed state", async () => {
|
||||
|
|
@ -110,15 +110,15 @@ describe("PLightbox (low-mock, jsdom-friendly)", () => {
|
|||
|
||||
it("KeyI is ignored when dialog is not visible", async () => {
|
||||
const wrapper = mountLightbox();
|
||||
expect(localStorage.getItem(infoKey)).toBeNull();
|
||||
expect(localStorage.getItem(sidebarKey)).toBeNull();
|
||||
await wrapper.vm.onShortCut({ code: "KeyI" });
|
||||
expect(localStorage.getItem(infoKey)).toBeNull();
|
||||
expect(localStorage.getItem(sidebarKey)).toBeNull();
|
||||
});
|
||||
|
||||
// Ctrl+H (#5580) toggles the PhotoSwipe Dynamic Caption overlay and
|
||||
// persists the choice to localStorage. Default state is visible
|
||||
// (hideCaption reads "lightbox.caption" with a strict !== "false"
|
||||
// check, so missing-key resolves to true). Mirrors the toggleInfo
|
||||
// check, so missing-key resolves to true). Mirrors the toggleSidebar
|
||||
// pattern so future shortcut additions can copy the same shape.
|
||||
it("toggleCaption updates hideCaption and localStorage when visible", async () => {
|
||||
const wrapper = mountLightbox();
|
||||
|
|
@ -223,9 +223,9 @@ describe("PLightbox (low-mock, jsdom-friendly)", () => {
|
|||
}
|
||||
});
|
||||
|
||||
it("calls preventDefault when info is open and an INPUT is focused", () => {
|
||||
it("calls preventDefault when sidebar is open and an INPUT is focused", () => {
|
||||
const wrapper = mountLightbox();
|
||||
wrapper.vm.info = true;
|
||||
wrapper.vm.sidebarVisible = true;
|
||||
const input = document.createElement("input");
|
||||
scratch.appendChild(input);
|
||||
input.focus();
|
||||
|
|
@ -234,9 +234,9 @@ describe("PLightbox (low-mock, jsdom-friendly)", () => {
|
|||
expect(ev.preventDefault).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("calls preventDefault when info is open and a TEXTAREA is focused", () => {
|
||||
it("calls preventDefault when sidebar is open and a TEXTAREA is focused", () => {
|
||||
const wrapper = mountLightbox();
|
||||
wrapper.vm.info = true;
|
||||
wrapper.vm.sidebarVisible = true;
|
||||
const ta = document.createElement("textarea");
|
||||
scratch.appendChild(ta);
|
||||
ta.focus();
|
||||
|
|
@ -247,11 +247,11 @@ describe("PLightbox (low-mock, jsdom-friendly)", () => {
|
|||
|
||||
// Note: isContentEditable behavior isn't reliably simulated by jsdom, so
|
||||
// the contenteditable branch of onPswpKeyDown is exercised in the browser
|
||||
// ui-tester run. The INPUT / TEXTAREA / non-editable / no-info / no-event
|
||||
// ui-tester run. The INPUT / TEXTAREA / non-editable / no-sidebar / no-event
|
||||
// cases here cover the predicate's two-class boundary in unit tests.
|
||||
it("does NOT call preventDefault when info is closed even with input focused", () => {
|
||||
it("does NOT call preventDefault when sidebar is closed even with input focused", () => {
|
||||
const wrapper = mountLightbox();
|
||||
wrapper.vm.info = false;
|
||||
wrapper.vm.sidebarVisible = false;
|
||||
const input = document.createElement("input");
|
||||
scratch.appendChild(input);
|
||||
input.focus();
|
||||
|
|
@ -262,7 +262,7 @@ describe("PLightbox (low-mock, jsdom-friendly)", () => {
|
|||
|
||||
it("does NOT call preventDefault when focus is on a non-editable element", () => {
|
||||
const wrapper = mountLightbox();
|
||||
wrapper.vm.info = true;
|
||||
wrapper.vm.sidebarVisible = true;
|
||||
const span = document.createElement("span");
|
||||
span.tabIndex = 0;
|
||||
scratch.appendChild(span);
|
||||
|
|
@ -274,7 +274,7 @@ describe("PLightbox (low-mock, jsdom-friendly)", () => {
|
|||
|
||||
it("tolerates a missing event argument", () => {
|
||||
const wrapper = mountLightbox();
|
||||
wrapper.vm.info = true;
|
||||
wrapper.vm.sidebarVisible = true;
|
||||
expect(() => wrapper.vm.$options.methods.onPswpKeyDown.call(wrapper.vm, undefined)).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
|
@ -416,12 +416,12 @@ describe("PLightbox (low-mock, jsdom-friendly)", () => {
|
|||
// exits it. The eye and ✓ Done controls live in the sidebar, so a
|
||||
// closed sidebar would otherwise leave the overlay mounted with no
|
||||
// way to disable it.
|
||||
it("hideInfo exits face-marker UI so the overlay tears down", async () => {
|
||||
it("hideSidebar exits face-marker UI so the overlay tears down", async () => {
|
||||
const wrapper = mountLightbox();
|
||||
const exitFaceMarkerMode = vi.fn();
|
||||
const ctx = {
|
||||
visible: true,
|
||||
info: true,
|
||||
sidebarVisible: true,
|
||||
faceMarkers: makeFaceMarkers({ active: true, isEdit: true, mode: FaceMarkerEdit }),
|
||||
exitFaceMarkerMode,
|
||||
confirmDiscardSidebar: () => Promise.resolve(true),
|
||||
|
|
@ -429,20 +429,20 @@ describe("PLightbox (low-mock, jsdom-friendly)", () => {
|
|||
resize: vi.fn(),
|
||||
focusContent: vi.fn(),
|
||||
};
|
||||
await wrapper.vm.$options.methods.hideInfo.call(ctx);
|
||||
expect(ctx.info).toBe(false);
|
||||
await wrapper.vm.$options.methods.hideSidebar.call(ctx);
|
||||
expect(ctx.sidebarVisible).toBe(false);
|
||||
expect(exitFaceMarkerMode).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
// Guard: hideInfo does not exit face-marker UI if the user cancels
|
||||
// Guard: hideSidebar does not exit face-marker UI if the user cancels
|
||||
// out of the discard prompt. Sidebar (and overlay) stay open.
|
||||
it("hideInfo keeps face-marker UI when confirmDiscardSidebar resolves false", async () => {
|
||||
it("hideSidebar keeps face-marker UI when confirmDiscardSidebar resolves false", async () => {
|
||||
const wrapper = mountLightbox();
|
||||
const exitFaceMarkerMode = vi.fn();
|
||||
const faceMarkers = makeFaceMarkers({ active: true, isEdit: true, mode: FaceMarkerEdit });
|
||||
const ctx = {
|
||||
visible: true,
|
||||
info: true,
|
||||
sidebarVisible: true,
|
||||
faceMarkers,
|
||||
exitFaceMarkerMode,
|
||||
confirmDiscardSidebar: () => Promise.resolve(false),
|
||||
|
|
@ -450,8 +450,8 @@ describe("PLightbox (low-mock, jsdom-friendly)", () => {
|
|||
resize: vi.fn(),
|
||||
focusContent: vi.fn(),
|
||||
};
|
||||
await wrapper.vm.$options.methods.hideInfo.call(ctx);
|
||||
expect(ctx.info).toBe(true);
|
||||
await wrapper.vm.$options.methods.hideSidebar.call(ctx);
|
||||
expect(ctx.sidebarVisible).toBe(true);
|
||||
expect(faceMarkers.mode).toBe(FaceMarkerEdit);
|
||||
expect(exitFaceMarkerMode).not.toHaveBeenCalled();
|
||||
});
|
||||
|
|
@ -706,7 +706,7 @@ describe("PLightbox (low-mock, jsdom-friendly)", () => {
|
|||
const toggleVideo = vi.fn();
|
||||
const ctx = {
|
||||
visible: true,
|
||||
info: false,
|
||||
sidebarVisible: false,
|
||||
faceMarkers: makeFaceMarkers({ active: true, ...modeFlags }),
|
||||
isShortcutDisabledInFaceMarkerMode: wrapper.vm.$options.methods.isShortcutDisabledInFaceMarkerMode,
|
||||
$view: { isActive: () => true },
|
||||
|
|
@ -737,17 +737,17 @@ describe("PLightbox (low-mock, jsdom-friendly)", () => {
|
|||
it("onShortCut still routes Escape + KeyI even when face-marker mode is active", () => {
|
||||
const wrapper = mountLightbox();
|
||||
const onEscapeKey = vi.fn();
|
||||
const toggleInfo = vi.fn();
|
||||
const toggleSidebar = vi.fn();
|
||||
const ctx = {
|
||||
faceMarkers: makeFaceMarkers({ active: true, isDisplay: true, mode: FaceMarkerDisplay }),
|
||||
isShortcutDisabledInFaceMarkerMode: wrapper.vm.$options.methods.isShortcutDisabledInFaceMarkerMode,
|
||||
onEscapeKey,
|
||||
toggleInfo,
|
||||
toggleSidebar,
|
||||
};
|
||||
wrapper.vm.$options.methods.onShortCut.call(ctx, { code: "Escape" });
|
||||
wrapper.vm.$options.methods.onShortCut.call(ctx, { code: "KeyI" });
|
||||
expect(onEscapeKey).toHaveBeenCalledTimes(1);
|
||||
expect(toggleInfo).toHaveBeenCalledTimes(1);
|
||||
expect(toggleSidebar).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -810,7 +810,7 @@ describe("PLightbox (low-mock, jsdom-friendly)", () => {
|
|||
const wrapper = mountLightbox();
|
||||
const ctx = {
|
||||
...wrapper.vm,
|
||||
info: true,
|
||||
sidebarVisible: true,
|
||||
models: [{ UID: "uid-curr" }, { UID: "uid-next" }],
|
||||
index: 0,
|
||||
$config: { ...wrapper.vm.$config, deny: () => true, allow: () => false },
|
||||
|
|
@ -827,7 +827,7 @@ describe("PLightbox (low-mock, jsdom-friendly)", () => {
|
|||
const wrapper = mountLightbox();
|
||||
const ctx = {
|
||||
...wrapper.vm,
|
||||
info: false,
|
||||
sidebarVisible: false,
|
||||
models: [{ UID: "uid-curr" }, { UID: "uid-next" }],
|
||||
index: 0,
|
||||
};
|
||||
|
|
@ -844,7 +844,7 @@ describe("PLightbox (low-mock, jsdom-friendly)", () => {
|
|||
const models = [{ UID: "uid-curr" }, { UID: "uid-next" }];
|
||||
const ctx = {
|
||||
...wrapper.vm,
|
||||
info: true,
|
||||
sidebarVisible: true,
|
||||
models,
|
||||
index: 0,
|
||||
$config: allowAllConfig,
|
||||
|
|
@ -993,7 +993,7 @@ describe("PLightbox (low-mock, jsdom-friendly)", () => {
|
|||
const models = [{ UID: "uid-curr" }, { UID: "uid-next" }];
|
||||
const ctx = {
|
||||
...wrapper.vm,
|
||||
info: true,
|
||||
sidebarVisible: true,
|
||||
models,
|
||||
index: 0,
|
||||
$config: allowAllConfig,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue