mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
Merge fix/128-sidebar-view-redirect: prevent repeated redirect when switching from grid to sidebar view
This commit is contained in:
@@ -13,6 +13,8 @@ import { useSettingsStore } from "@/stores/settings-store";
|
|||||||
// Tools shown prominently as "quick actions" at the top
|
// Tools shown prominently as "quick actions" at the top
|
||||||
const QUICK_ACTION_IDS = ["resize", "compress", "convert", "remove-background"];
|
const QUICK_ACTION_IDS = ["resize", "compress", "convert", "remove-background"];
|
||||||
|
|
||||||
|
let hasAppliedDefaultRedirect = false;
|
||||||
|
|
||||||
export function HomePage() {
|
export function HomePage() {
|
||||||
const {
|
const {
|
||||||
setFiles,
|
setFiles,
|
||||||
@@ -37,7 +39,13 @@ export function HomePage() {
|
|||||||
}, [fetchSettings, fetchFeatures]);
|
}, [fetchSettings, fetchFeatures]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (settingsLoaded && defaultToolView === "fullscreen" && files.length === 0) {
|
if (
|
||||||
|
!hasAppliedDefaultRedirect &&
|
||||||
|
settingsLoaded &&
|
||||||
|
defaultToolView === "fullscreen" &&
|
||||||
|
files.length === 0
|
||||||
|
) {
|
||||||
|
hasAppliedDefaultRedirect = true;
|
||||||
navigate("/fullscreen", { replace: true });
|
navigate("/fullscreen", { replace: true });
|
||||||
}
|
}
|
||||||
}, [settingsLoaded, defaultToolView, files.length, navigate]);
|
}, [settingsLoaded, defaultToolView, files.length, navigate]);
|
||||||
|
|||||||
@@ -0,0 +1,158 @@
|
|||||||
|
// @vitest-environment jsdom
|
||||||
|
import type React from "react";
|
||||||
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
let settingsState = {
|
||||||
|
fetch: vi.fn(),
|
||||||
|
defaultToolView: "sidebar" as "sidebar" | "fullscreen",
|
||||||
|
loaded: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
vi.mock("@/stores/settings-store", () => ({
|
||||||
|
useSettingsStore: () => settingsState,
|
||||||
|
}));
|
||||||
|
|
||||||
|
let fileState = {
|
||||||
|
setFiles: vi.fn(),
|
||||||
|
files: [] as File[],
|
||||||
|
reset: vi.fn(),
|
||||||
|
originalBlobUrl: null as string | null,
|
||||||
|
selectedFileName: null as string | null,
|
||||||
|
selectedFileSize: 0,
|
||||||
|
currentEntry: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
vi.mock("@/stores/file-store", () => ({
|
||||||
|
useFileStore: () => fileState,
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("@/stores/features-store", () => ({
|
||||||
|
useFeaturesStore: () => ({
|
||||||
|
fetch: vi.fn(),
|
||||||
|
bundles: [],
|
||||||
|
installing: {},
|
||||||
|
queued: [],
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("@/components/layout/app-layout", () => ({
|
||||||
|
AppLayout: ({ children }: React.PropsWithChildren) => (
|
||||||
|
<div data-testid="app-layout">{children}</div>
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("@/components/common/image-viewer", () => ({
|
||||||
|
ImageViewer: () => null,
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("@/components/common/multi-image-viewer", () => ({
|
||||||
|
MultiImageViewer: () => null,
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("@/lib/icon-map", () => ({
|
||||||
|
ICON_MAP: new Proxy(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
get: () => (props: { className?: string }) => <span {...props} />,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
|
||||||
|
import { act, cleanup, render } from "@testing-library/react";
|
||||||
|
import { MemoryRouter, Route, Routes, useLocation } from "react-router-dom";
|
||||||
|
import { HomePage } from "@/pages/home-page";
|
||||||
|
|
||||||
|
let currentPath = "/";
|
||||||
|
|
||||||
|
function LocationSpy() {
|
||||||
|
const location = useLocation();
|
||||||
|
currentPath = location.pathname;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderInRouter(initialPath = "/") {
|
||||||
|
currentPath = initialPath;
|
||||||
|
return render(
|
||||||
|
<MemoryRouter initialEntries={[initialPath]}>
|
||||||
|
<LocationSpy />
|
||||||
|
<Routes>
|
||||||
|
<Route path="/" element={<HomePage />} />
|
||||||
|
<Route path="/fullscreen" element={<div data-testid="fullscreen-page" />} />
|
||||||
|
</Routes>
|
||||||
|
</MemoryRouter>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
afterEach(cleanup);
|
||||||
|
|
||||||
|
// Tests are ordered intentionally: the module-level `hasAppliedDefaultRedirect`
|
||||||
|
// flag starts false and is set true by the "redirects on first mount" test.
|
||||||
|
// Tests before it verify conditions that independently prevent the redirect
|
||||||
|
// (so the flag stays false). The final test verifies the flag prevents repeat redirects.
|
||||||
|
describe("HomePage default view redirect", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
settingsState = {
|
||||||
|
fetch: vi.fn(),
|
||||||
|
defaultToolView: "sidebar",
|
||||||
|
loaded: false,
|
||||||
|
};
|
||||||
|
fileState = {
|
||||||
|
setFiles: vi.fn(),
|
||||||
|
files: [],
|
||||||
|
reset: vi.fn(),
|
||||||
|
originalBlobUrl: null,
|
||||||
|
selectedFileName: null,
|
||||||
|
selectedFileSize: 0,
|
||||||
|
currentEntry: null,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does NOT redirect when settings have not loaded yet", () => {
|
||||||
|
settingsState.loaded = false;
|
||||||
|
settingsState.defaultToolView = "fullscreen";
|
||||||
|
|
||||||
|
renderInRouter("/");
|
||||||
|
|
||||||
|
expect(currentPath).toBe("/");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does NOT redirect when default view is sidebar", () => {
|
||||||
|
settingsState.loaded = true;
|
||||||
|
settingsState.defaultToolView = "sidebar";
|
||||||
|
|
||||||
|
renderInRouter("/");
|
||||||
|
|
||||||
|
expect(currentPath).toBe("/");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does NOT redirect when files are loaded even with fullscreen default", () => {
|
||||||
|
settingsState.loaded = true;
|
||||||
|
settingsState.defaultToolView = "fullscreen";
|
||||||
|
fileState.files = [new File([new ArrayBuffer(1024)], "test.png", { type: "image/png" })];
|
||||||
|
fileState.originalBlobUrl = "blob:fake-url";
|
||||||
|
fileState.selectedFileName = "test.png";
|
||||||
|
fileState.selectedFileSize = 1024;
|
||||||
|
|
||||||
|
renderInRouter("/");
|
||||||
|
|
||||||
|
expect(currentPath).toBe("/");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("redirects to /fullscreen on first mount when default view is fullscreen", () => {
|
||||||
|
settingsState.loaded = true;
|
||||||
|
settingsState.defaultToolView = "fullscreen";
|
||||||
|
|
||||||
|
renderInRouter("/");
|
||||||
|
|
||||||
|
expect(currentPath).toBe("/fullscreen");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does NOT redirect on subsequent mounts after initial redirect (issue #128)", () => {
|
||||||
|
settingsState.loaded = true;
|
||||||
|
settingsState.defaultToolView = "fullscreen";
|
||||||
|
|
||||||
|
renderInRouter("/");
|
||||||
|
|
||||||
|
expect(currentPath).toBe("/");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -108,6 +108,7 @@ export default defineConfig({
|
|||||||
sharp: path.join(apiNodeModules, "sharp"),
|
sharp: path.join(apiNodeModules, "sharp"),
|
||||||
react: path.join(webNodeModules, "react"),
|
react: path.join(webNodeModules, "react"),
|
||||||
"react-dom": path.join(webNodeModules, "react-dom"),
|
"react-dom": path.join(webNodeModules, "react-dom"),
|
||||||
|
"react-router-dom": path.join(webNodeModules, "react-router-dom"),
|
||||||
zustand: path.join(webNodeModules, "zustand"),
|
zustand: path.join(webNodeModules, "zustand"),
|
||||||
"posthog-js": path.join(webNodeModules, "posthog-js"),
|
"posthog-js": path.join(webNodeModules, "posthog-js"),
|
||||||
"@sentry/react": path.join(webNodeModules, "@sentry/react"),
|
"@sentry/react": path.join(webNodeModules, "@sentry/react"),
|
||||||
|
|||||||
Reference in New Issue
Block a user