Skip to content

Conversation

@zombieJ
Copy link
Member

@zombieJ zombieJ commented Nov 7, 2025

ref ant-design/ant-design#55618

Summary by CodeRabbit

发布说明

  • Bug Fixes

    • 改进下拉菜单滚动行为:当菜单关闭时,不再自动滚动到活动项,提升用户体验。
  • Tests

    • 优化相关测试用例以确保滚动行为的正确性。

@coderabbitai
Copy link

coderabbitai bot commented Nov 7, 2025

Walkthrough

本 PR 为 DropdownMenu 组件添加了 opened 属性,用于控制菜单打开状态。KeywordTrigger 现在跟踪下拉菜单的可见性状态,并将其传递给 DropdownMenu。滚动到视图的行为现已被限制为仅在菜单打开时执行。相关测试已重构为异步。

Changes

Cohort / File(s) 变更摘要
菜单状态管理
src/DropdownMenu.tsx, src/KeywordTrigger.tsx
DropdownMenu 新增必需的 opened: boolean 属性;当菜单关闭时,scrollIntoView 行为被守卫条件跳过;KeywordTrigger 引入内部 opened 状态,通过 afterOpenChange 事件监听菜单打开/关闭,并将状态传递给 DropdownMenu;MenuRef 改为类型导入
测试更新
tests/DropdownMenu.spec.tsx
将 "should scroll into view when navigating with keyboard" 测试从同步转换为异步;简化交互流程,使用循环推进伪计时器,消除测试中的测量状态验证;保留 scrollIntoView 的最终断言

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant KT as KeywordTrigger
    participant DM as DropdownMenu
    participant RC as RC Menu
    
    User->>KT: 触发下拉菜单
    KT->>RC: 打开菜单
    RC->>KT: afterOpenChange(true)
    KT->>KT: 设置 opened=true
    KT->>DM: 传递 opened=true
    
    User->>KT: 键盘导航 (Down)
    KT->>DM: activeIndex 变化
    DM->>DM: 检查:opened=true 且 menuRef 存在
    DM->>RC: scrollIntoView({block, inline})
    
    User->>KT: 关闭菜单
    RC->>KT: afterOpenChange(false)
    KT->>KT: 设置 opened=false
    KT->>DM: 传递 opened=false
    
    User->>KT: 键盘导航 (Down)
    KT->>DM: activeIndex 变化
    DM->>DM: 检查:opened=false,跳过滚动
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

需要关注的具体区域:

  • src/KeywordTrigger.tsxafterOpenChange 事件的钩接逻辑是否正确处理了所有打开/关闭场景
  • src/DropdownMenu.tsx 中新的 opened 守卫条件是否完全防止了非预期的滚动行为
  • tests/DropdownMenu.spec.tsx 中异步测试的转换是否充分覆盖原有的测试意图,特别是关于滚动位置的验证逻辑

Possibly related PRs

Poem

🐰✨ 菜单开启才滚动,
状态传递如春风;
KeywordTrigger 细心看,
DropdownMenu 听指挥,
测试异步显真功! 🎉

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 标题 'fix: open trigger force scroll' 与变更集的主要目的相关联,涉及修复与打开触发器和滚动行为相关的问题。
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix-scroll

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link

Summary of Changes

Hello @zombieJ, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request resolves an issue where the mentions dropdown component would inadvertently trigger a scroll-into-view action upon its initial opening. By implementing a new opened prop and integrating it into the dropdown's scroll effect, the component now correctly prevents premature scrolling. This ensures that the scroll-into-view functionality is activated only when a user actively navigates through options within an already visible menu, leading to a more predictable and improved user experience.

Highlights

  • Controlled Scroll Behavior: Introduced an opened prop to the DropdownMenu component, allowing its internal scroll-into-view logic to be conditionally executed only when the menu is actively visible.
  • State Management for Trigger: The KeywordTrigger component now manages an opened state, which is updated via the afterOpenChange event of the underlying Trigger component and passed down to DropdownMenu.
  • Refined Test Logic: The test suite for DropdownMenu has been updated to accurately reflect the new behavior, specifically asserting that scrollIntoView is not invoked when the dropdown initially opens, but only during subsequent keyboard navigation within the open menu.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@codecov
Copy link

codecov bot commented Nov 7, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.48%. Comparing base (1d31bf2) to head (799b9f5).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #304      +/-   ##
==========================================
+ Coverage   98.09%   98.48%   +0.38%     
==========================================
  Files           8        8              
  Lines         263      264       +1     
  Branches       64       64              
==========================================
+ Hits          258      260       +2     
+ Misses          5        4       -1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request aims to fix an issue with forced scrolling when the mentions dropdown is triggered. The changes introduce an opened state that is synchronized with the dropdown's visibility, and use this state to delay the scrollIntoView call until after the dropdown has finished its opening animation. This seems like a reasonable approach to prevent scrolling issues during the animation. My main feedback is on the test file tests/DropdownMenu.spec.tsx, where the test logic has become confusing and less comprehensive. I've left a comment with suggestions to improve the test's clarity and coverage.

Comment on lines +39 to +44
for (let i = 0; i < 10; i++) {
await act(async () => {
jest.advanceTimersByTime(1000);
await Promise.resolve();
});
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This test has a few issues that could be improved:

  1. Confusing loop: The for loop advancing timers seems incorrect. To wait for the dropdown to open, a single act block that runs all timers is sufficient and much clearer.
  2. Misleading test name: The test is named ...navigating with keyboard but no longer simulates keyboard navigation. It now only tests the scroll behavior on open. Consider renaming the test to something like should scroll into view after dropdown opens.
  3. Reduced coverage: The part of the test that verified scrolling up with ArrowUp has been removed. It would be beneficial to have a test that covers keyboard navigation in both directions, perhaps in a separate test case.

Here's a suggestion to simplify the timer logic:

Suggested change
for (let i = 0; i < 10; i++) {
await act(async () => {
jest.advanceTimersByTime(1000);
await Promise.resolve();
});
}
// Wait for the dropdown to fully open by running all timers.
// This will trigger `afterOpenChange` and any subsequent effects.
await act(async () => {
jest.runAllTimers();
await Promise.resolve();
});

@zombieJ zombieJ merged commit ce7a383 into master Nov 7, 2025
7 of 8 checks passed
@zombieJ zombieJ deleted the fix-scroll branch November 7, 2025 07:58
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
src/DropdownMenu.tsx (1)

38-38: 移除多余的空行。

第 38 行的空行是多余的,建议移除以保持代码整洁。

应用此差异来移除空行:

     const activeItem = menuRef.current?.findItem?.({ key: activeOption.key });
-
     if (activeItem) {
tests/DropdownMenu.spec.tsx (2)

39-44: 定时器推进逻辑过于脆弱。

使用循环推进 10 秒(10 次迭代 × 1000ms)的定时器看起来很随意,并且使测试变得脆弱。如果动画或 afterOpenChange 的调用时机发生变化,这个测试可能会失败或产生误报。

建议使用更明确的方式来等待下拉菜单的打开状态,例如:

  1. 使用 waitFor 等待特定的 DOM 状态
  2. 或者直接测试在不同 opened 状态下的滚动行为,而不依赖定时器

考虑以下更稳健的测试方法:

// 等待下拉菜单实际打开并完成动画
await waitFor(() => {
  // 检查下拉菜单是否可见或者检查特定的 DOM 状态
  expect(container.querySelector('.rc-mentions-dropdown-menu')).toBeVisible();
});

// 然后验证滚动行为
expect(scrollIntoViewMock).toHaveBeenCalledWith({
  block: 'nearest',
  inline: 'nearest',
});

39-50: 增加键盘导航的测试覆盖。

虽然当前测试验证了下拉菜单打开后的滚动行为,但根据测试名称,应该也测试键盘导航(例如按 Down/Up 箭头键)时的滚动行为。建议添加一个独立的测试来验证当 opened=trueactiveIndex 通过键盘导航改变时,scrollIntoView 会被正确调用。

示例测试代码:

it('should scroll when activeIndex changes via keyboard navigation', async () => {
  const { container } = render(
    <UnstableContext.Provider value={{ open: true }}>
      <Mentions defaultValue="@" options={generateOptions} />
    </UnstableContext.Provider>,
  );

  const scrollIntoViewMock = jest
    .spyOn(HTMLElement.prototype, 'scrollIntoView')
    .mockImplementation(jest.fn());

  simulateInput(container, '@');
  
  // 等待下拉菜单打开
  await act(async () => {
    jest.runAllTimers();
    await Promise.resolve();
  });

  scrollIntoViewMock.mockClear();

  // 模拟按下 Down 箭头键
  fireEvent.keyDown(container.querySelector('textarea'), {
    key: 'ArrowDown',
    keyCode: KeyCode.DOWN,
  });

  // 验证滚动被触发
  expect(scrollIntoViewMock).toHaveBeenCalled();

  scrollIntoViewMock.mockRestore();
});
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1d31bf2 and 799b9f5.

📒 Files selected for processing (3)
  • src/DropdownMenu.tsx (2 hunks)
  • src/KeywordTrigger.tsx (2 hunks)
  • tests/DropdownMenu.spec.tsx (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
tests/DropdownMenu.spec.tsx (1)
tests/util.ts (1)
  • simulateInput (3-24)
src/DropdownMenu.tsx (1)
src/Mentions.tsx (1)
  • DataDrivenOptionProps (43-45)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test / react component workflow
🔇 Additional comments (8)
src/KeywordTrigger.tsx (3)

72-72: 状态初始化正确。

使用 useState(false) 初始化 opened 状态是合理的,确保在下拉菜单首次打开前不会触发滚动。


75-79: 正确传递 opened 状态到 DropdownMenu。

opened 属性传递给 DropdownMenu 组件使其能够根据下拉菜单的实际打开状态来控制滚动行为。


103-103: afterOpenChange 的行为已验证,实现方式正确,无需修改。

根据 @rc-component/trigger v3.0.0 的文档,afterOpenChange 回调在内部 visible 状态变化时被调用,而非在动画完成后调用。这是该库的标准行为。

当前代码实现(afterOpenChange={setOpened})正确地追踪了弹窗的可见性变化,opened 状态随后被传递给 DropdownMenu 使用。这种模式符合库的 API 设计,不存在状态不一致或不必要的滚动行为问题。

src/DropdownMenu.tsx (4)

1-1: 正确使用 type 导入。

MenuRef 改为 type 导入是良好的 TypeScript 实践,有助于减少运行时包大小。


9-9: opened 属性应该是必需的。

opened: boolean 被定义为必需属性,这确保了调用者必须明确提供下拉菜单的打开状态。这是正确的设计决策。


33-34: 滚动防护逻辑正确。

通过检查 !opened 来防止在下拉菜单未打开时执行滚动操作,这正确地解决了强制滚动的问题。


45-45: 依赖数组正确包含 opened。

opened 添加到依赖数组确保当下拉菜单状态改变时,effect 会重新执行。这对于在下拉菜单打开后触发滚动行为至关重要。

tests/DropdownMenu.spec.tsx (1)

35-37: 良好的防护验证。

验证在输入触发后 scrollIntoView 不会立即被调用,这正确地测试了修复的核心行为——防止在下拉菜单打开时强制滚动。

});

it('should scroll into view when navigating with keyboard', () => {
it('should scroll into view when navigating with keyboard', async () => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

测试描述与实际行为不匹配。

测试名称为 "should scroll into view when navigating with keyboard",但实际测试中并未模拟键盘导航(例如按下 Down/Up 箭头键)。测试只是模拟输入并等待定时器推进。建议将测试名称修改为更准确地反映其实际验证的行为,例如 "should scroll into view after dropdown opens"。

应用此差异来更新测试名称:

-  it('should scroll into view when navigating with keyboard', async () => {
+  it('should scroll into view after dropdown opens', async () => {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it('should scroll into view when navigating with keyboard', async () => {
it('should scroll into view after dropdown opens', async () => {
🤖 Prompt for AI Agents
In tests/DropdownMenu.spec.tsx around line 22, the test title "should scroll
into view when navigating with keyboard" is inaccurate because the test does not
simulate keyboard navigation; rename the test to reflect actual behavior, e.g.,
change the it(...) description to "should scroll into view after dropdown opens"
so the test name matches what is being verified.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants