Merge pull request #72 from HappyOnigiri/test/add-missing-ui-component-tests
test: add missing UI component tests
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
createMockGalleryContext,
|
||||
type MockGalleryContext,
|
||||
} from "../../test/helpers/renderWithGallery";
|
||||
import { DeleteConfirmModal } from "./DeleteConfirmModal";
|
||||
|
||||
// Mock useGallery
|
||||
vi.mock("../../store/GalleryContext", () => ({
|
||||
useGallery: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock useEscapeToClose
|
||||
vi.mock("../../hooks/useEscapeToClose", () => ({
|
||||
useEscapeToClose: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock useLightTableStore
|
||||
vi.mock("../../features/light-table/store", () => ({
|
||||
useLightTableStore: Object.assign(
|
||||
vi.fn(() => ({})),
|
||||
{
|
||||
getState: vi.fn(() => ({ buckets: {} })),
|
||||
},
|
||||
),
|
||||
}));
|
||||
|
||||
// Mock delete helpers
|
||||
vi.mock("../../features/images/hooks/deleteHelpers", () => ({
|
||||
deleteImagesAndSyncLightTable: vi.fn().mockResolvedValue(undefined),
|
||||
}));
|
||||
|
||||
// Mock images API
|
||||
vi.mock("../../features/images/api/imagesApi", () => ({
|
||||
fetchLineage: vi.fn().mockResolvedValue([]),
|
||||
}));
|
||||
|
||||
import { useGallery } from "../../store/GalleryContext";
|
||||
|
||||
describe("DeleteConfirmModal", () => {
|
||||
let ctx: MockGalleryContext;
|
||||
|
||||
beforeEach(() => {
|
||||
ctx = createMockGalleryContext({
|
||||
activeModal: { type: "delete_confirm", imageIds: [1, 2], hasLineage: false },
|
||||
});
|
||||
vi.mocked(useGallery).mockReturnValue(ctx);
|
||||
});
|
||||
|
||||
it("renders 'Move to Trash' header by default", () => {
|
||||
render(<DeleteConfirmModal imageIds={[1, 2]} hasLineage={false} />);
|
||||
const heading = document.querySelector(".meld-modal-header h2");
|
||||
expect(heading?.textContent).toContain("Move to Trash");
|
||||
});
|
||||
|
||||
it("renders 'Permanent Deletion' header when isPermanent is true", () => {
|
||||
render(<DeleteConfirmModal imageIds={[1, 2]} hasLineage={false} isPermanent />);
|
||||
const heading = document.querySelector(".meld-modal-header h2");
|
||||
expect(heading?.textContent).toContain("Permanent Deletion");
|
||||
});
|
||||
|
||||
it("displays the number of selected items", () => {
|
||||
render(<DeleteConfirmModal imageIds={[1, 2, 3]} hasLineage={false} />);
|
||||
expect(screen.getByText("3")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("dispatches CLOSE_MODAL when Cancel button is clicked", async () => {
|
||||
render(<DeleteConfirmModal imageIds={[1]} hasLineage={false} />);
|
||||
await userEvent.click(screen.getByText("Cancel"));
|
||||
expect(ctx.dispatch).toHaveBeenCalledWith({ type: "CLOSE_MODAL" });
|
||||
});
|
||||
|
||||
it("shows lineage buttons when hasLineage is true", () => {
|
||||
render(<DeleteConfirmModal imageIds={[1]} hasLineage />);
|
||||
expect(screen.getByText("Move All Related")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("does not show lineage buttons when hasLineage is false", () => {
|
||||
render(<DeleteConfirmModal imageIds={[1]} hasLineage={false} />);
|
||||
expect(screen.queryByText("Move All Related")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows permanent delete labels when isPermanent is true with lineage", () => {
|
||||
render(<DeleteConfirmModal imageIds={[1]} hasLineage isPermanent />);
|
||||
expect(screen.getByText("Delete Permanently")).toBeInTheDocument();
|
||||
expect(screen.getByText("Delete All Related")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows warning message for permanent deletion", () => {
|
||||
render(<DeleteConfirmModal imageIds={[1]} hasLineage={false} isPermanent />);
|
||||
expect(screen.getByText(/permanently deleted/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows info message for trash mode", () => {
|
||||
render(<DeleteConfirmModal imageIds={[1]} hasLineage={false} />);
|
||||
expect(screen.getByText(/moved to the trash bin/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("dispatches CLOSE_MODAL when close (X) button is clicked", async () => {
|
||||
render(<DeleteConfirmModal imageIds={[1]} hasLineage={false} />);
|
||||
// Close button is the one with the X icon
|
||||
const closeButton = document.querySelector(".meld-modal-close");
|
||||
expect(closeButton).toBeTruthy();
|
||||
await userEvent.click(closeButton as HTMLElement);
|
||||
expect(ctx.dispatch).toHaveBeenCalledWith({ type: "CLOSE_MODAL" });
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,157 @@
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
createMockGalleryContext,
|
||||
type MockGalleryContext,
|
||||
} from "../../test/helpers/renderWithGallery";
|
||||
import { GalleryModals } from "./GalleryModals";
|
||||
|
||||
// Mock useGallery
|
||||
vi.mock("../../store/GalleryContext", () => ({
|
||||
useGallery: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock useWorkflowExecution
|
||||
vi.mock("../../features/workflows/hooks/useWorkflowExecution", () => ({
|
||||
useWorkflowExecution: vi.fn(() => ({
|
||||
executeWorkflow: vi.fn(),
|
||||
})),
|
||||
}));
|
||||
|
||||
// Mock child modals to keep tests focused on GalleryModals logic
|
||||
vi.mock("../../features/importer/components/ImportModal", () => ({
|
||||
ImportModal: () => <div data-testid="import-modal">ImportModal</div>,
|
||||
}));
|
||||
vi.mock("../../features/light-table/components/DownloadModal", () => ({
|
||||
DownloadModal: () => <div data-testid="download-modal">DownloadModal</div>,
|
||||
}));
|
||||
vi.mock("../../features/mask-editor/components/MaskEditorModal", () => ({
|
||||
MaskEditorModal: () => <div data-testid="mask-editor-modal">MaskEditorModal</div>,
|
||||
}));
|
||||
vi.mock("../../features/mask-editor/components/MaskSequenceModal", () => ({
|
||||
MaskSequenceModal: () => <div data-testid="mask-sequence-modal">MaskSequenceModal</div>,
|
||||
}));
|
||||
vi.mock("../../features/settings/components/SettingsModal", () => ({
|
||||
SettingsModal: () => <div data-testid="settings-modal">SettingsModal</div>,
|
||||
}));
|
||||
vi.mock("../../features/tags/components/TagEditModal", () => ({
|
||||
TagEditModal: () => <div data-testid="tag-edit-modal">TagEditModal</div>,
|
||||
}));
|
||||
vi.mock("../../features/viewer/components/NoteEditModal", () => ({
|
||||
NoteEditModal: () => <div data-testid="note-edit-modal">NoteEditModal</div>,
|
||||
}));
|
||||
vi.mock("../../features/workflows/components/NodeSelectionModal", () => ({
|
||||
NodeSelectionModal: () => <div data-testid="node-selection-modal">NodeSelectionModal</div>,
|
||||
}));
|
||||
vi.mock("../../features/workflows/components/WorkflowSelectionModal", () => ({
|
||||
WorkflowSelectionModal: () => (
|
||||
<div data-testid="workflow-selection-modal">WorkflowSelectionModal</div>
|
||||
),
|
||||
}));
|
||||
vi.mock("../../features/workflows/utils/injectImageToGraph", () => ({
|
||||
injectImageToGraph: vi.fn(),
|
||||
}));
|
||||
vi.mock("./ConfirmModal", () => ({
|
||||
ConfirmModal: ({ message }: { message: string }) => (
|
||||
<div data-testid="confirm-modal">{message}</div>
|
||||
),
|
||||
}));
|
||||
vi.mock("./DeleteConfirmModal", () => ({
|
||||
DeleteConfirmModal: () => <div data-testid="delete-confirm-modal">DeleteConfirmModal</div>,
|
||||
}));
|
||||
vi.mock("./ErrorModal", () => ({
|
||||
ErrorModal: ({ message }: { message: string }) => <div data-testid="error-modal">{message}</div>,
|
||||
}));
|
||||
vi.mock("./ParentSelectionModal", () => ({
|
||||
ParentSelectionModal: () => <div data-testid="parent-selection-modal">ParentSelectionModal</div>,
|
||||
}));
|
||||
vi.mock("./Toast", () => ({
|
||||
Toast: () => <div data-testid="toast">Toast</div>,
|
||||
}));
|
||||
|
||||
import { useGallery } from "../../store/GalleryContext";
|
||||
|
||||
describe("GalleryModals", () => {
|
||||
let ctx: MockGalleryContext;
|
||||
|
||||
it("returns null when viewer is open (viewerImageId is not null)", () => {
|
||||
ctx = createMockGalleryContext({ viewerImageId: 42 });
|
||||
vi.mocked(useGallery).mockReturnValue(ctx);
|
||||
|
||||
const { container } = render(<GalleryModals />);
|
||||
expect(container.innerHTML).toBe("");
|
||||
});
|
||||
|
||||
it("renders nothing extra when activeModal is none and no toast", () => {
|
||||
ctx = createMockGalleryContext({
|
||||
viewerImageId: null,
|
||||
activeModal: { type: "none" },
|
||||
confirmModal: null,
|
||||
toastMessage: null,
|
||||
});
|
||||
vi.mocked(useGallery).mockReturnValue(ctx);
|
||||
|
||||
render(<GalleryModals />);
|
||||
expect(screen.queryByTestId("confirm-modal")).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId("toast")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders import modal when activeModal type is import", () => {
|
||||
ctx = createMockGalleryContext({
|
||||
viewerImageId: null,
|
||||
activeModal: { type: "import" },
|
||||
});
|
||||
vi.mocked(useGallery).mockReturnValue(ctx);
|
||||
|
||||
render(<GalleryModals />);
|
||||
expect(screen.getByTestId("import-modal")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders settings modal when activeModal type is settings", () => {
|
||||
ctx = createMockGalleryContext({
|
||||
viewerImageId: null,
|
||||
activeModal: { type: "settings" },
|
||||
});
|
||||
vi.mocked(useGallery).mockReturnValue(ctx);
|
||||
|
||||
render(<GalleryModals />);
|
||||
expect(screen.getByTestId("settings-modal")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders error modal via portal when activeModal type is error", () => {
|
||||
ctx = createMockGalleryContext({
|
||||
viewerImageId: null,
|
||||
activeModal: { type: "error", message: "Something went wrong" },
|
||||
});
|
||||
vi.mocked(useGallery).mockReturnValue(ctx);
|
||||
|
||||
render(<GalleryModals />);
|
||||
expect(screen.getByTestId("error-modal")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders toast when toastMessage is present", () => {
|
||||
ctx = createMockGalleryContext({
|
||||
viewerImageId: null,
|
||||
toastMessage: "Success!",
|
||||
});
|
||||
vi.mocked(useGallery).mockReturnValue(ctx);
|
||||
|
||||
render(<GalleryModals />);
|
||||
expect(screen.getByTestId("toast")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders confirm modal when confirmModal state is present", () => {
|
||||
ctx = createMockGalleryContext({
|
||||
viewerImageId: null,
|
||||
confirmModal: {
|
||||
message: "Confirm this?",
|
||||
onConfirm: vi.fn(),
|
||||
onCancel: vi.fn(),
|
||||
},
|
||||
});
|
||||
vi.mocked(useGallery).mockReturnValue(ctx);
|
||||
|
||||
render(<GalleryModals />);
|
||||
expect(screen.getByTestId("confirm-modal")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,221 @@
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { createTestImage, resetImageIdCounter } from "../../../test/factories/image";
|
||||
import { GalleryPanel } from "./GalleryPanel";
|
||||
|
||||
// Mock useGalleryLogic
|
||||
const mockUseGalleryLogic = vi.fn();
|
||||
vi.mock("../hooks/useGalleryLogic", () => ({
|
||||
useGalleryLogic: (...args: unknown[]) => mockUseGalleryLogic(...args),
|
||||
}));
|
||||
|
||||
// Mock useLightTableStore
|
||||
vi.mock("../../light-table/store", () => ({
|
||||
useLightTableStore: vi.fn(() => false),
|
||||
}));
|
||||
|
||||
// Mock child components to keep tests focused on GalleryPanel logic
|
||||
vi.mock("../../../components/shared/GalleryModals", () => ({
|
||||
GalleryModals: () => <div data-testid="gallery-modals" />,
|
||||
}));
|
||||
vi.mock("../../importer/components/ImportProgress", () => ({
|
||||
ImportProgress: () => null,
|
||||
}));
|
||||
vi.mock("../../light-table/components/LightTable", () => ({
|
||||
LightTable: () => null,
|
||||
}));
|
||||
vi.mock("../../search/components/FavoritesContextMenu", () => ({
|
||||
FavoritesContextMenu: () => null,
|
||||
}));
|
||||
vi.mock("../../search/components/SearchBar", () => ({
|
||||
SearchBar: () => <div data-testid="search-bar" />,
|
||||
}));
|
||||
vi.mock("../../tags/components/TagManagerView", () => ({
|
||||
TagManagerView: () => <div data-testid="tag-manager" />,
|
||||
}));
|
||||
vi.mock("../../viewer/components/ImageViewer", () => ({
|
||||
ImageViewer: () => <div data-testid="image-viewer" />,
|
||||
}));
|
||||
vi.mock("./BulkActionBar", () => ({
|
||||
BulkActionBar: () => <div data-testid="bulk-action-bar" />,
|
||||
}));
|
||||
vi.mock("./VirtualizedGalleryList", () => ({
|
||||
VirtualizedGalleryList: () => <div data-testid="virtualized-gallery-list" />,
|
||||
}));
|
||||
vi.mock("../../../styles/Gallery.css", () => ({}));
|
||||
vi.mock("../../../logger", () => ({
|
||||
logger: { log: vi.fn(), error: vi.fn(), warn: vi.fn() },
|
||||
}));
|
||||
|
||||
// Create default mock return value for useGalleryLogic
|
||||
function createMockLogic(overrides = {}) {
|
||||
const loadMoreRef = { current: null };
|
||||
return {
|
||||
state: {
|
||||
images: [],
|
||||
isLoading: false,
|
||||
error: null,
|
||||
viewerImageId: null,
|
||||
viewScope: "default",
|
||||
searchQuery: "",
|
||||
selectedIds: new Set(),
|
||||
settings: {
|
||||
"gallery.view_mode": "grid_details",
|
||||
},
|
||||
activeModal: { type: "none" },
|
||||
pagination: { hasMore: false, limit: 50 },
|
||||
favorites: [],
|
||||
toastMessage: null,
|
||||
},
|
||||
dispatch: vi.fn(),
|
||||
refreshImages: vi.fn(),
|
||||
loadMoreImages: vi.fn(),
|
||||
updateSetting: vi.fn(),
|
||||
viewMode: "gallery",
|
||||
setViewMode: vi.fn(),
|
||||
lastSearchQuery: "",
|
||||
setLastSearchQuery: vi.fn(),
|
||||
displayedImages: [],
|
||||
visibleImages: [],
|
||||
isSearchActive: false,
|
||||
loadMoreRef,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe("GalleryPanel", () => {
|
||||
beforeEach(() => {
|
||||
resetImageIdCounter();
|
||||
mockUseGalleryLogic.mockReturnValue(createMockLogic());
|
||||
});
|
||||
|
||||
it("shows loading message when loading with no images", () => {
|
||||
mockUseGalleryLogic.mockReturnValue(
|
||||
createMockLogic({
|
||||
state: {
|
||||
images: [],
|
||||
isLoading: true,
|
||||
error: null,
|
||||
viewerImageId: null,
|
||||
viewScope: "default",
|
||||
searchQuery: "",
|
||||
selectedIds: new Set(),
|
||||
settings: { "gallery.view_mode": "grid_details" },
|
||||
activeModal: { type: "none" },
|
||||
pagination: { hasMore: false, limit: 50 },
|
||||
favorites: [],
|
||||
toastMessage: null,
|
||||
},
|
||||
displayedImages: [],
|
||||
visibleImages: [],
|
||||
}),
|
||||
);
|
||||
|
||||
render(<GalleryPanel />);
|
||||
expect(screen.getByText("Loading images...")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows empty message when no images are found", () => {
|
||||
render(<GalleryPanel />);
|
||||
expect(screen.getByText("No images found.")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders VirtualizedGalleryList when images are present", () => {
|
||||
const img = createTestImage();
|
||||
mockUseGalleryLogic.mockReturnValue(
|
||||
createMockLogic({
|
||||
state: {
|
||||
images: [img],
|
||||
isLoading: false,
|
||||
error: null,
|
||||
viewerImageId: null,
|
||||
viewScope: "default",
|
||||
searchQuery: "",
|
||||
selectedIds: new Set(),
|
||||
settings: { "gallery.view_mode": "grid_details" },
|
||||
activeModal: { type: "none" },
|
||||
pagination: { hasMore: false, limit: 50 },
|
||||
favorites: [],
|
||||
toastMessage: null,
|
||||
},
|
||||
displayedImages: [img],
|
||||
visibleImages: [img],
|
||||
}),
|
||||
);
|
||||
|
||||
render(<GalleryPanel />);
|
||||
expect(screen.getByTestId("virtualized-gallery-list")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows trash indicator when viewScope is trash", () => {
|
||||
mockUseGalleryLogic.mockReturnValue(
|
||||
createMockLogic({
|
||||
state: {
|
||||
images: [],
|
||||
isLoading: false,
|
||||
error: null,
|
||||
viewerImageId: null,
|
||||
viewScope: "trash",
|
||||
searchQuery: "",
|
||||
selectedIds: new Set(),
|
||||
settings: { "gallery.view_mode": "grid_details" },
|
||||
activeModal: { type: "none" },
|
||||
pagination: { hasMore: false, limit: 50 },
|
||||
favorites: [],
|
||||
toastMessage: null,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
render(<GalleryPanel />);
|
||||
expect(screen.getByText("Trash Bin")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows error message when error is present", () => {
|
||||
mockUseGalleryLogic.mockReturnValue(
|
||||
createMockLogic({
|
||||
state: {
|
||||
images: [],
|
||||
isLoading: false,
|
||||
error: "Something went wrong",
|
||||
viewerImageId: null,
|
||||
viewScope: "default",
|
||||
searchQuery: "",
|
||||
selectedIds: new Set(),
|
||||
settings: { "gallery.view_mode": "grid_details" },
|
||||
activeModal: { type: "none" },
|
||||
pagination: { hasMore: false, limit: 50 },
|
||||
favorites: [],
|
||||
toastMessage: null,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
render(<GalleryPanel />);
|
||||
expect(screen.getByText("Something went wrong")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders ImageViewer when viewerImageId is set", () => {
|
||||
mockUseGalleryLogic.mockReturnValue(
|
||||
createMockLogic({
|
||||
state: {
|
||||
images: [],
|
||||
isLoading: false,
|
||||
error: null,
|
||||
viewerImageId: 1,
|
||||
viewScope: "default",
|
||||
searchQuery: "",
|
||||
selectedIds: new Set(),
|
||||
settings: { "gallery.view_mode": "grid_details" },
|
||||
activeModal: { type: "none" },
|
||||
pagination: { hasMore: false, limit: 50 },
|
||||
favorites: [],
|
||||
toastMessage: null,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
render(<GalleryPanel />);
|
||||
expect(screen.getByTestId("image-viewer")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,118 @@
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { createTestImage, resetImageIdCounter } from "../../../test/factories/image";
|
||||
import type { MeldImage, Settings } from "../../../types";
|
||||
import { VirtualizedGalleryList } from "./VirtualizedGalleryList";
|
||||
|
||||
// jsdom does not provide ResizeObserver
|
||||
beforeAll(() => {
|
||||
globalThis.ResizeObserver = class {
|
||||
observe() {}
|
||||
unobserve() {}
|
||||
disconnect() {}
|
||||
} as unknown as typeof ResizeObserver;
|
||||
});
|
||||
|
||||
// Mock @tanstack/react-virtual
|
||||
vi.mock("@tanstack/react-virtual", () => ({
|
||||
useVirtualizer: vi.fn(() => ({
|
||||
getVirtualItems: () => [],
|
||||
getTotalSize: () => 0,
|
||||
scrollToIndex: vi.fn(),
|
||||
measureElement: vi.fn(),
|
||||
})),
|
||||
}));
|
||||
|
||||
// Mock ImageCard
|
||||
vi.mock("../../../components/shared/ImageCard", () => ({
|
||||
ImageCard: ({ image }: { image: MeldImage }) => (
|
||||
<div data-testid={`image-card-${image.id}`}>{image.filename}</div>
|
||||
),
|
||||
}));
|
||||
|
||||
const defaultSettings: Settings = {
|
||||
"sidebar.thumbnail_size": 100,
|
||||
"gallery.view_mode": "grid_details",
|
||||
} as Settings;
|
||||
|
||||
describe("VirtualizedGalleryList", () => {
|
||||
const loadMoreRef = { current: null } as React.RefObject<HTMLDivElement>;
|
||||
|
||||
beforeEach(() => {
|
||||
resetImageIdCounter();
|
||||
});
|
||||
|
||||
it("shows loading indicator when isLoading is true", () => {
|
||||
render(
|
||||
<VirtualizedGalleryList
|
||||
visibleImages={[createTestImage()]}
|
||||
settings={defaultSettings}
|
||||
loadMoreRef={loadMoreRef}
|
||||
viewerImageId={null}
|
||||
isLoading
|
||||
hasMore
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByText("Loading more...")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows end of gallery when not loading and hasMore is false", () => {
|
||||
const img = createTestImage();
|
||||
render(
|
||||
<VirtualizedGalleryList
|
||||
visibleImages={[img]}
|
||||
settings={defaultSettings}
|
||||
loadMoreRef={loadMoreRef}
|
||||
viewerImageId={null}
|
||||
isLoading={false}
|
||||
hasMore={false}
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByText("End of gallery")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders the scroll container", () => {
|
||||
render(
|
||||
<VirtualizedGalleryList
|
||||
visibleImages={[]}
|
||||
settings={defaultSettings}
|
||||
loadMoreRef={loadMoreRef}
|
||||
viewerImageId={null}
|
||||
isLoading={false}
|
||||
hasMore={false}
|
||||
/>,
|
||||
);
|
||||
const scrollEl = document.querySelector(".meld-gallery__list-scroll");
|
||||
expect(scrollEl).toBeTruthy();
|
||||
});
|
||||
|
||||
it("sets thumbnail size CSS variable", () => {
|
||||
const settings = { ...defaultSettings, "sidebar.thumbnail_size": 150 } as Settings;
|
||||
render(
|
||||
<VirtualizedGalleryList
|
||||
visibleImages={[]}
|
||||
settings={settings}
|
||||
loadMoreRef={loadMoreRef}
|
||||
viewerImageId={null}
|
||||
isLoading={false}
|
||||
hasMore={false}
|
||||
/>,
|
||||
);
|
||||
const scrollEl = document.querySelector(".meld-gallery__list-scroll") as HTMLElement;
|
||||
expect(scrollEl?.style.getPropertyValue("--meld-thumbnail-size")).toBe("150px");
|
||||
});
|
||||
|
||||
it("does not show end message when images list is empty", () => {
|
||||
render(
|
||||
<VirtualizedGalleryList
|
||||
visibleImages={[]}
|
||||
settings={defaultSettings}
|
||||
loadMoreRef={loadMoreRef}
|
||||
viewerImageId={null}
|
||||
isLoading={false}
|
||||
hasMore={false}
|
||||
/>,
|
||||
);
|
||||
expect(screen.queryByText("End of gallery")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,241 @@
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { createTestImage, resetImageIdCounter } from "../../../test/factories/image";
|
||||
import {
|
||||
createMockGalleryContext,
|
||||
type MockGalleryContext,
|
||||
} from "../../../test/helpers/renderWithGallery";
|
||||
import type { Settings } from "../../../types";
|
||||
import { ImageViewer } from "./ImageViewer";
|
||||
|
||||
// Mock useGallery
|
||||
vi.mock("../../../store/GalleryContext", () => ({
|
||||
useGallery: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock useImageViewerLogic
|
||||
const mockUseImageViewerLogic = vi.fn();
|
||||
vi.mock("../hooks/useImageViewerLogic", () => ({
|
||||
useImageViewerLogic: (...args: unknown[]) => mockUseImageViewerLogic(...args),
|
||||
}));
|
||||
|
||||
// Mock useWorkflowExecution
|
||||
vi.mock("../../workflows/hooks/useWorkflowExecution", () => ({
|
||||
useWorkflowExecution: vi.fn(() => ({
|
||||
executeWorkflow: vi.fn(),
|
||||
})),
|
||||
}));
|
||||
|
||||
// Mock child components
|
||||
vi.mock("./ImageCardMenu", () => ({
|
||||
ImageCardMenu: () => <div data-testid="image-card-menu" />,
|
||||
}));
|
||||
vi.mock("./ViewerInfoPanel", () => ({
|
||||
ViewerInfoPanel: () => <div data-testid="viewer-info-panel" />,
|
||||
}));
|
||||
vi.mock("./ViewerThumbnailStrip", () => ({
|
||||
ViewerThumbnailStrip: () => <div data-testid="viewer-thumbnail-strip" />,
|
||||
}));
|
||||
vi.mock("./ViewerCheatSheet", () => ({
|
||||
ViewerCheatSheet: () => <div data-testid="viewer-cheat-sheet" />,
|
||||
}));
|
||||
vi.mock("./NoteEditModal", () => ({
|
||||
NoteEditModal: () => <div data-testid="note-edit-modal" />,
|
||||
}));
|
||||
|
||||
// Mock modal components
|
||||
vi.mock("../../../components/shared/DeleteConfirmModal", () => ({
|
||||
DeleteConfirmModal: () => <div data-testid="delete-confirm-modal" />,
|
||||
}));
|
||||
vi.mock("../../../components/shared/ErrorModal", () => ({
|
||||
ErrorModal: () => <div data-testid="error-modal" />,
|
||||
}));
|
||||
vi.mock("../../../components/shared/ParentSelectionModal", () => ({
|
||||
ParentSelectionModal: () => <div data-testid="parent-selection-modal" />,
|
||||
}));
|
||||
vi.mock("../../importer/components/ImportModal", () => ({
|
||||
ImportModal: () => <div data-testid="import-modal" />,
|
||||
}));
|
||||
vi.mock("../../settings/components/SettingsModal", () => ({
|
||||
SettingsModal: () => <div data-testid="settings-modal" />,
|
||||
}));
|
||||
vi.mock("../../tags/components/TagEditModal", () => ({
|
||||
TagEditModal: () => <div data-testid="tag-edit-modal" />,
|
||||
}));
|
||||
vi.mock("../../mask-editor/components/MaskEditorModal", () => ({
|
||||
MaskEditorModal: () => <div data-testid="mask-editor-modal" />,
|
||||
}));
|
||||
vi.mock("../../mask-editor/components/MaskSequenceModal", () => ({
|
||||
MaskSequenceModal: () => <div data-testid="mask-sequence-modal" />,
|
||||
}));
|
||||
vi.mock("../../workflows/components/NodeSelectionModal", () => ({
|
||||
NodeSelectionModal: () => <div data-testid="node-selection-modal" />,
|
||||
}));
|
||||
vi.mock("../../workflows/components/WorkflowSelectionModal", () => ({
|
||||
WorkflowSelectionModal: () => <div data-testid="workflow-selection-modal" />,
|
||||
}));
|
||||
vi.mock("../../workflows/utils/injectImageToGraph", () => ({
|
||||
injectImageToGraph: vi.fn(),
|
||||
}));
|
||||
vi.mock("../../../utils/url", () => ({
|
||||
getImageViewUrl: (img: { filename: string }) => `/api/view/${img.filename}`,
|
||||
}));
|
||||
|
||||
import { useGallery } from "../../../store/GalleryContext";
|
||||
import { initialState } from "../../../store/galleryReducer";
|
||||
|
||||
function createMockViewerLogic(image: ReturnType<typeof createTestImage> | null, overrides = {}) {
|
||||
return {
|
||||
isFullscreen: false,
|
||||
showDetails: false,
|
||||
setShowDetails: vi.fn(),
|
||||
showThumbnails: false,
|
||||
setShowThumbnailsOverride: vi.fn(),
|
||||
isLoadingLineage: false,
|
||||
isJumping: false,
|
||||
isMenuOpen: false,
|
||||
setIsMenuOpen: vi.fn(),
|
||||
activeShortcutKey: null,
|
||||
setLastDeletedImages: vi.fn(),
|
||||
overlayRef: { current: null },
|
||||
handleNext: vi.fn(),
|
||||
handlePrevious: vi.fn(),
|
||||
handleTagEdit: vi.fn(),
|
||||
handleEditNotes: vi.fn(),
|
||||
handleRestore: vi.fn(),
|
||||
handleRestoreWorkflow: vi.fn(),
|
||||
handleAddUnifiedLoader: vi.fn(),
|
||||
handleSendToWorkflow: vi.fn(),
|
||||
handleRunWithWorkflow: vi.fn(),
|
||||
handleRunWithMask: vi.fn(),
|
||||
handleEditSource: vi.fn(),
|
||||
handleDelete: vi.fn(),
|
||||
toggleFullscreen: vi.fn(),
|
||||
image,
|
||||
windowedThumbnails: [],
|
||||
parentChain: [],
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
// Settings with icons visible
|
||||
const iconsVisibleSettings: Settings = {
|
||||
...initialState.settings,
|
||||
"viewer.show_icons": true,
|
||||
"viewer.small_image_mode": "fit",
|
||||
"viewer.thumbnail_window_size": 5,
|
||||
};
|
||||
|
||||
// Settings with icons hidden
|
||||
const iconsHiddenSettings: Settings = {
|
||||
...initialState.settings,
|
||||
"viewer.show_icons": false,
|
||||
"viewer.small_image_mode": "fit",
|
||||
"viewer.thumbnail_window_size": 5,
|
||||
};
|
||||
|
||||
describe("ImageViewer", () => {
|
||||
let ctx: MockGalleryContext;
|
||||
|
||||
beforeEach(() => {
|
||||
resetImageIdCounter();
|
||||
});
|
||||
|
||||
it("renders nothing when image is null", () => {
|
||||
ctx = createMockGalleryContext({
|
||||
viewerImageId: 1,
|
||||
activeModal: { type: "none" },
|
||||
});
|
||||
vi.mocked(useGallery).mockReturnValue(ctx);
|
||||
mockUseImageViewerLogic.mockReturnValue(createMockViewerLogic(null));
|
||||
|
||||
const { container } = render(<ImageViewer />);
|
||||
expect(container.innerHTML).toBe("");
|
||||
});
|
||||
|
||||
it("renders the viewer overlay when image is present", () => {
|
||||
const img = createTestImage();
|
||||
ctx = createMockGalleryContext({
|
||||
viewerImageId: img.id,
|
||||
viewerMode: "gallery",
|
||||
activeModal: { type: "none" },
|
||||
viewScope: "default",
|
||||
settings: iconsVisibleSettings,
|
||||
});
|
||||
vi.mocked(useGallery).mockReturnValue(ctx);
|
||||
mockUseImageViewerLogic.mockReturnValue(createMockViewerLogic(img));
|
||||
|
||||
render(<ImageViewer />);
|
||||
const overlay = document.querySelector(".meld-viewer-overlay");
|
||||
expect(overlay).toBeTruthy();
|
||||
});
|
||||
|
||||
it("renders the image element with correct src", () => {
|
||||
const img = createTestImage();
|
||||
ctx = createMockGalleryContext({
|
||||
viewerImageId: img.id,
|
||||
viewerMode: "gallery",
|
||||
activeModal: { type: "none" },
|
||||
viewScope: "default",
|
||||
settings: iconsVisibleSettings,
|
||||
});
|
||||
vi.mocked(useGallery).mockReturnValue(ctx);
|
||||
mockUseImageViewerLogic.mockReturnValue(createMockViewerLogic(img));
|
||||
|
||||
render(<ImageViewer />);
|
||||
const imgEl = document.querySelector(".meld-viewer-image") as HTMLImageElement;
|
||||
expect(imgEl).toBeTruthy();
|
||||
expect(imgEl.src).toContain(img.filename);
|
||||
});
|
||||
|
||||
it("renders navigation buttons when icons are shown", () => {
|
||||
const img = createTestImage();
|
||||
ctx = createMockGalleryContext({
|
||||
viewerImageId: img.id,
|
||||
viewerMode: "gallery",
|
||||
activeModal: { type: "none" },
|
||||
viewScope: "default",
|
||||
settings: iconsVisibleSettings,
|
||||
});
|
||||
vi.mocked(useGallery).mockReturnValue(ctx);
|
||||
mockUseImageViewerLogic.mockReturnValue(createMockViewerLogic(img));
|
||||
|
||||
render(<ImageViewer />);
|
||||
expect(document.querySelector(".meld-viewer-nav--prev")).toBeTruthy();
|
||||
expect(document.querySelector(".meld-viewer-nav--next")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("renders close button with correct title", () => {
|
||||
const img = createTestImage();
|
||||
ctx = createMockGalleryContext({
|
||||
viewerImageId: img.id,
|
||||
viewerMode: "gallery",
|
||||
activeModal: { type: "none" },
|
||||
viewScope: "default",
|
||||
settings: iconsVisibleSettings,
|
||||
});
|
||||
vi.mocked(useGallery).mockReturnValue(ctx);
|
||||
mockUseImageViewerLogic.mockReturnValue(createMockViewerLogic(img));
|
||||
|
||||
render(<ImageViewer />);
|
||||
const closeBtn = screen.getByTitle("Close (Esc)");
|
||||
expect(closeBtn).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("does not render navigation buttons when icons are hidden", () => {
|
||||
const img = createTestImage();
|
||||
ctx = createMockGalleryContext({
|
||||
viewerImageId: img.id,
|
||||
viewerMode: "gallery",
|
||||
activeModal: { type: "none" },
|
||||
viewScope: "default",
|
||||
settings: iconsHiddenSettings,
|
||||
});
|
||||
vi.mocked(useGallery).mockReturnValue(ctx);
|
||||
mockUseImageViewerLogic.mockReturnValue(createMockViewerLogic(img));
|
||||
|
||||
render(<ImageViewer />);
|
||||
expect(document.querySelector(".meld-viewer-nav--prev")).toBeNull();
|
||||
expect(document.querySelector(".meld-viewer-nav--next")).toBeNull();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user