Skip to content

Commit ff9c79e

Browse files
committed
test(mobile): Home restores its offset only when search returns to rest
Move the restore into useRestoreOffsetAfterSearch so its mount and search-return behavior is tested without rendering all of Home.
1 parent 152577a commit ff9c79e

4 files changed

Lines changed: 81 additions & 11 deletions

File tree

‎apps/mobile/src/features/home/HomeScreen.tsx‎

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ import { SwipeableScrollGateProvider, useSwipeableScrollGate } from "./thread-sw
8585
import { useFabScroll } from "./FabScrollContext";
8686
import { useWorkspaceTabs } from "../workspace/WorkspaceTabsContext";
8787
import { ThreadListFilters } from "./ThreadListFilters";
88+
import { useRestoreOffsetAfterSearch } from "./useRestoreOffsetAfterSearch";
8889

8990
interface HomeScreenProps {
9091
readonly projects: ReadonlyArray<EnvironmentProject>;
@@ -316,19 +317,12 @@ export function HomeScreen(props: HomeScreenProps) {
316317
const listRef = useRef<LegendListRef>(null);
317318
const searchMode = workspace?.state.tabs.threads.searchMode;
318319
const restoredOffset = workspace?.state.tabs.threads.scrollOffset;
319-
// Only a return from search restores. On mount, UIKit already rests the list
320-
// below the header, and a scroll here would use an unmeasured header inset.
321-
const previousSearchMode = useRef(searchMode);
322-
// oxlint-disable-next-line t3code/no-unreasoned-state-sync -- restores the thread list's saved scroll offset through the list ref when search goes back to resting
323-
useEffect(() => {
324-
const restoringSearch = previousSearchMode.current !== "resting" && searchMode === "resting";
325-
previousSearchMode.current = searchMode;
326-
if (!restoringSearch || restoredOffset === undefined) return;
320+
useRestoreOffsetAfterSearch(searchMode, restoredOffset, (offset) =>
327321
listRef.current?.scrollToOffset({
328-
offset: restoredOffset - (Platform.OS === "ios" ? insets.top : 0),
322+
offset: offset - (Platform.OS === "ios" ? insets.top : 0),
329323
animated: false,
330-
});
331-
}, [searchMode, insets.top, restoredOffset]);
324+
}),
325+
);
332326
const swipeRowActivation = createSwipeRowActivation();
333327
const activateVisibleRows = (rows: ReadonlyArray<ThreadListV2ListItem>) => {
334328
const state = listRef.current?.getState();
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { act } from "react";
2+
import { createRoot } from "react-dom/client";
3+
import { expect, it, vi } from "vite-plus/test";
4+
5+
import type { WorkspaceSearchMode } from "../workspace/workspace-tabs-state";
6+
import { useRestoreOffsetAfterSearch } from "./useRestoreOffsetAfterSearch";
7+
8+
type Props = { mode: WorkspaceSearchMode; offset: number | undefined };
9+
10+
function mountProbe() {
11+
const restore = vi.fn();
12+
function Probe(props: Props) {
13+
useRestoreOffsetAfterSearch(props.mode, props.offset, restore);
14+
return null;
15+
}
16+
const root = createRoot(document.createElement("div"));
17+
return {
18+
restore,
19+
render: (props: Props) => act(async () => root.render(<Probe {...props} />)),
20+
[Symbol.dispose]: () => act(() => root.unmount()),
21+
};
22+
}
23+
24+
it("leaves the list where UIKit rested it when Home mounts at rest", async () => {
25+
using screen = mountProbe();
26+
await screen.render({ mode: "resting", offset: 0 });
27+
await screen.render({ mode: "resting", offset: 0 });
28+
expect(screen.restore).not.toHaveBeenCalled();
29+
});
30+
31+
it("restores the saved offset when active or expanded search returns to resting", async () => {
32+
using screen = mountProbe();
33+
await screen.render({ mode: "resting", offset: 120 });
34+
await screen.render({ mode: "active", offset: 120 });
35+
await screen.render({ mode: "expanded", offset: 120 });
36+
expect(screen.restore).not.toHaveBeenCalled();
37+
38+
await screen.render({ mode: "resting", offset: 80 });
39+
expect(screen.restore).toHaveBeenCalledExactlyOnceWith(80);
40+
41+
await screen.render({ mode: "active", offset: 80 });
42+
await screen.render({ mode: "resting", offset: 200 });
43+
expect(screen.restore).toHaveBeenCalledTimes(2);
44+
expect(screen.restore).toHaveBeenLastCalledWith(200);
45+
});
46+
47+
it("does not scroll again when the saved offset changes while resting", async () => {
48+
using screen = mountProbe();
49+
await screen.render({ mode: "active", offset: 50 });
50+
await screen.render({ mode: "resting", offset: 50 });
51+
await screen.render({ mode: "resting", offset: 300 });
52+
expect(screen.restore).toHaveBeenCalledExactlyOnceWith(50);
53+
});
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { useEffect, useRef } from "react";
2+
3+
import type { WorkspaceSearchMode } from "../workspace/workspace-tabs-state";
4+
5+
/**
6+
* Restores a list's saved offset when search returns to resting. Mounting at rest restores
7+
* nothing: UIKit already rests the list below the header, and a scroll there would use an
8+
* unmeasured header inset.
9+
*/
10+
export function useRestoreOffsetAfterSearch(
11+
searchMode: WorkspaceSearchMode | undefined,
12+
savedOffset: number | undefined,
13+
restore: (offset: number) => void,
14+
) {
15+
const previousSearchMode = useRef(searchMode);
16+
// oxlint-disable-next-line t3code/no-unreasoned-state-sync -- scrolls the list to the offset saved when search opened once the tab returns to resting
17+
useEffect(() => {
18+
const restoringSearch = previousSearchMode.current !== "resting" && searchMode === "resting";
19+
previousSearchMode.current = searchMode;
20+
if (restoringSearch && savedOffset !== undefined) restore(savedOffset);
21+
}, [searchMode, savedOffset, restore]);
22+
}

‎apps/mobile/vite.config.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const reactTests = [
3131
"src/features/terminal/useTerminalExitNavigation.test.tsx",
3232
"src/features/threads/use-thread-list-now.test.tsx",
3333
"src/features/home/home-list-options.test.tsx",
34+
"src/features/home/useRestoreOffsetAfterSearch.test.tsx",
3435
"src/features/connection/ConnectionsNewRouteScreen.test.tsx",
3536
];
3637

0 commit comments

Comments
 (0)