Repository navigation
fix(diff): keep the collapsed section header in view #24466
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| import { describe, expect, it } from 'vitest' | ||
| import type { VirtualizedScrollAnchor } from '@/hooks/useVirtualizedScrollAnchor' | ||
| import { | ||
| pinScrollAnchorWhenCollapsingSection, | ||
| planCombinedDiffSectionToggle | ||
| } from './combined-diff-collapse-scroll-anchor' | ||
|
|
||
| function anchor(offset: number): NonNullable<VirtualizedScrollAnchor> { | ||
| return { key: 'src/app.ts', offset, scrollTop: 2_000 } | ||
| } | ||
|
|
||
| describe('pinScrollAnchorWhenCollapsingSection', () => { | ||
| it('pins the header when the anchored section is the one being collapsed', () => { | ||
| const current = anchor(1_800) | ||
| pinScrollAnchorWhenCollapsingSection(current, { collapsed: false, key: 'src/app.ts' }) | ||
| expect(current.offset).toBe(0) | ||
| expect(current.scrollTop).toBe(2_000) | ||
| expect(current.key).toBe('src/app.ts') | ||
| }) | ||
|
|
||
| it('leaves the anchor when that section is expanding', () => { | ||
|
Comment on lines
+13
to
+21
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Knowledge Base Used: Desktop renderer experience Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The viewer calls There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That addresses the ordering invariant: the anchor is pinned before the updater publishes the collapsed section, while the updater still preserves the section data and only toggles You’re also right that this does not assert the rendered pixel position; it verifies the helper’s input to the existing structural restore rather than exercising the Electron viewport. Given the focused scope and the five anchor tests, I consider the unit coverage sufficient and withdraw the request for an additional viewer-render test. |
||
| const current = anchor(0) | ||
| pinScrollAnchorWhenCollapsingSection(current, { collapsed: true, key: 'src/app.ts' }) | ||
| expect(current.offset).toBe(0) | ||
| }) | ||
|
|
||
| it('leaves the anchor when a different section collapses', () => { | ||
| const current = anchor(400) | ||
| pinScrollAnchorWhenCollapsingSection(current, { collapsed: false, key: 'src/other.ts' }) | ||
| expect(current.offset).toBe(400) | ||
| }) | ||
|
|
||
| it('pins a mid-section anchor before the collapsed flag is published', () => { | ||
| const current = anchor(1_800) | ||
| const open = { collapsed: false, key: 'src/app.ts', marker: 'body' } | ||
| const plan = planCombinedDiffSectionToggle({ | ||
| index: 0, | ||
| sections: [open], | ||
| anchor: current | ||
| }) | ||
|
|
||
| expect(current.offset).toBe(0) | ||
| expect(current.scrollTop).toBe(2_000) | ||
| expect(plan.shouldLoadAfterExpand).toBe(false) | ||
|
|
||
| const published = plan.next([open]) | ||
| expect(published[0]?.collapsed).toBe(true) | ||
| expect(published[0]?.marker).toBe('body') | ||
| }) | ||
|
|
||
| it('does nothing without an anchor or a section', () => { | ||
| expect(() => pinScrollAnchorWhenCollapsingSection(null, undefined)).not.toThrow() | ||
| const current = anchor(40) | ||
| pinScrollAnchorWhenCollapsingSection(current, undefined) | ||
| expect(current.offset).toBe(40) | ||
| }) | ||
| }) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| import type { VirtualizedScrollAnchor } from '@/hooks/useVirtualizedScrollAnchor' | ||
|
|
||
| type CollapsibleSection = { | ||
| collapsed: boolean | ||
| key: string | ||
| } | ||
|
|
||
| /** | ||
| * Collapsing the section the viewport is inside drops that section's body. | ||
| * The recorded offset still points into the body, so restore lands on later | ||
| * sections. Pin the header (offset 0) and leave scrollTop alone: restore | ||
| * treats a matching scrollTop as "the user has not moved" and then applies | ||
| * the offset. Expanding, or collapsing some other section, keeps the anchor | ||
| * so a later section stays where it was. | ||
| */ | ||
| export function pinScrollAnchorWhenCollapsingSection( | ||
| anchor: VirtualizedScrollAnchor, | ||
| section: CollapsibleSection | undefined | ||
| ): void { | ||
| if (!anchor || !section || section.collapsed || anchor.key !== section.key) { | ||
| return | ||
| } | ||
| anchor.offset = 0 | ||
| } | ||
|
|
||
| export function planCombinedDiffSectionToggle<T extends CollapsibleSection>(input: { | ||
| index: number | ||
| sections: readonly T[] | ||
| anchor: VirtualizedScrollAnchor | ||
| }): { | ||
| next: (sections: readonly T[]) => T[] | ||
| shouldLoadAfterExpand: boolean | ||
| } { | ||
| const section = input.sections[input.index] | ||
| const shouldLoadAfterExpand = section?.collapsed ?? false | ||
| pinScrollAnchorWhenCollapsingSection(input.anchor, section) | ||
| return { | ||
| shouldLoadAfterExpand, | ||
| next: (sections) => | ||
| sections.map((row, index) => | ||
| index === input.index ? { ...row, collapsed: !row.collapsed } : row | ||
| ) | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.