Skip to content

Commit 7ec3d45

Browse files
committed
fix(ui): preserves scroll position on repo lock/move/unlock
1 parent 142e8f3 commit 7ec3d45

File tree

2 files changed

+61
-5
lines changed

2 files changed

+61
-5
lines changed

src/app/components/shared/RepoLockControls.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ interface RepoLockControlsProps {
77
repoFullName: string;
88
}
99

10+
const withScrollLock = (fn: () => void) => {
11+
const y = window.scrollY;
12+
fn();
13+
window.scrollTo(0, y);
14+
};
15+
1016
export default function RepoLockControls(props: RepoLockControlsProps) {
1117
const lockInfo = createMemo(() => {
1218
const list = viewState.lockedRepos[props.tab];
@@ -26,7 +32,7 @@ export default function RepoLockControls(props: RepoLockControlsProps) {
2632
<Tooltip content="Pin to top">
2733
<button
2834
class="btn btn-ghost btn-xs opacity-0 group-hover/repo-header:opacity-100 focus:opacity-100 max-sm:opacity-60 sm:max-lg:opacity-60 transition-opacity"
29-
onClick={() => lockRepo(props.tab, props.repoFullName)}
35+
onClick={() => withScrollLock(() => lockRepo(props.tab, props.repoFullName))}
3036
aria-label={`Pin ${props.repoFullName} to top of list`}
3137
>
3238
{/* Heroicons 20px solid: lock-open */}
@@ -40,7 +46,7 @@ export default function RepoLockControls(props: RepoLockControlsProps) {
4046
<Tooltip content="Unpin">
4147
<button
4248
class="btn btn-ghost btn-xs"
43-
onClick={() => unlockRepo(props.tab, props.repoFullName)}
49+
onClick={() => withScrollLock(() => unlockRepo(props.tab, props.repoFullName))}
4450
aria-label={`Unpin ${props.repoFullName}`}
4551
>
4652
{/* Heroicons 20px solid: lock-closed */}
@@ -52,7 +58,7 @@ export default function RepoLockControls(props: RepoLockControlsProps) {
5258
<Tooltip content={lockInfo().isFirst ? "Already at top of pinned list" : "Move up"}>
5359
<button
5460
class="btn btn-ghost btn-xs"
55-
onClick={() => moveLockedRepo(props.tab, props.repoFullName, "up")}
61+
onClick={() => withScrollLock(() => moveLockedRepo(props.tab, props.repoFullName, "up"))}
5662
disabled={lockInfo().isFirst}
5763
aria-label={`Move ${props.repoFullName} up`}
5864
>
@@ -65,7 +71,7 @@ export default function RepoLockControls(props: RepoLockControlsProps) {
6571
<Tooltip content={lockInfo().isLast ? "Already at bottom of pinned list" : "Move down"}>
6672
<button
6773
class="btn btn-ghost btn-xs"
68-
onClick={() => moveLockedRepo(props.tab, props.repoFullName, "down")}
74+
onClick={() => withScrollLock(() => moveLockedRepo(props.tab, props.repoFullName, "down"))}
6975
disabled={lockInfo().isLast}
7076
aria-label={`Move ${props.repoFullName} down`}
7177
>

tests/components/shared/RepoLockControls.test.tsx

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { describe, it, expect, vi, beforeEach } from "vitest";
1+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
22
import { render, screen, fireEvent } from "@solidjs/testing-library";
33
import RepoLockControls from "../../../src/app/components/shared/RepoLockControls";
44
import { resetViewState, viewState, lockRepo } from "../../../src/app/stores/view";
@@ -105,3 +105,53 @@ describe("RepoLockControls", () => {
105105
expect(parentClick).not.toHaveBeenCalled();
106106
});
107107
});
108+
109+
describe("RepoLockControls — scroll preservation", () => {
110+
beforeEach(() => {
111+
resetViewState();
112+
document.documentElement.scrollTop = 500;
113+
vi.spyOn(window, "scrollTo");
114+
});
115+
116+
afterEach(() => {
117+
vi.restoreAllMocks();
118+
document.documentElement.scrollTop = 0;
119+
});
120+
121+
it("preserves scroll position when locking a repo", () => {
122+
render(() => (
123+
<RepoLockControls tab="issues" repoFullName="owner/repo" />
124+
));
125+
fireEvent.click(screen.getByLabelText("Pin owner/repo to top of list"));
126+
expect(window.scrollTo).toHaveBeenCalledWith(0, 500);
127+
});
128+
129+
it("preserves scroll position when unlocking a repo", () => {
130+
lockRepo("issues", "owner/repo");
131+
render(() => (
132+
<RepoLockControls tab="issues" repoFullName="owner/repo" />
133+
));
134+
fireEvent.click(screen.getByLabelText("Unpin owner/repo"));
135+
expect(window.scrollTo).toHaveBeenCalledWith(0, 500);
136+
});
137+
138+
it("preserves scroll position when moving repo up", () => {
139+
lockRepo("issues", "owner/a");
140+
lockRepo("issues", "owner/b");
141+
render(() => (
142+
<RepoLockControls tab="issues" repoFullName="owner/b" />
143+
));
144+
fireEvent.click(screen.getByLabelText("Move owner/b up"));
145+
expect(window.scrollTo).toHaveBeenCalledWith(0, 500);
146+
});
147+
148+
it("preserves scroll position when moving repo down", () => {
149+
lockRepo("issues", "owner/a");
150+
lockRepo("issues", "owner/b");
151+
render(() => (
152+
<RepoLockControls tab="issues" repoFullName="owner/a" />
153+
));
154+
fireEvent.click(screen.getByLabelText("Move owner/a down"));
155+
expect(window.scrollTo).toHaveBeenCalledWith(0, 500);
156+
});
157+
});

0 commit comments

Comments
 (0)