Skip to content

Conversation

@ug-hero
Copy link

@ug-hero ug-hero commented Dec 9, 2025

close ant-design/ant-design#56134
96592087a1104b3fbf01f6a0553529a1

Summary by CodeRabbit

  • 新功能

    • 在组件上下文中透传输入框引用,组件树内可访问输入框以改善交互体验。
  • 修复

    • 优化下拉菜单滚动逻辑:仅在输入框确实聚焦时执行滚动,避免失焦或鼠标交互时的意外滚动。
  • 测试

    • 强化测试覆盖:新增“失焦不滚动”验证、DOM API mock 及定时器与事件处理清理。

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Dec 9, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

此 PR 在 MentionsContext 中新增并传递 textareaRef;在 DropdownMenu 的滚动 effect 中加入焦点守护(仅当文档焦点位于该 textarea 时才调用 scrollIntoView),并补充/调整测试以验证有/无焦点时的滚动行为。

Changes

内聚 / 文件 变更摘要
Context 接口
src/MentionsContext.ts
导入 TextAreaRef 类型;在 MentionsContextProps 中添加公开字段 textareaRef: React.MutableRefObject<TextAreaRef>
上下文值传递
src/Mentions.tsx
InternalMentionsMentionsContext.Provider 值中包含 textareaRef,将 textarea 引用通过上下文向下传递
焦点感知滚动守护
src/DropdownMenu.tsx
在 effect 中加入焦点检查:只有当 document.activeElement === textareaRef.current.nativeElement 时才调用 scrollIntoView;将 textareaRef 加入依赖数组
测试覆盖
tests/DropdownMenu.spec.tsx
调整测试:在一个用例中使用 autoFocus 验证会触发滚动,新增用例在无焦点情况下断言不触发 scrollIntoView;维护并清理对 scrollIntoView 和定时器的 mock

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 分钟

  • 检查 MentionsContextProps 的类型扩展对外部 TypeScript 消费者的兼容性(TextAreaRef 引入)。
  • 验证 DropdownMenu 中对 textareaRef.current.nativeElement 的空值防护,防止读取 null/undefined。
  • 审核测试对 focus/定时器与 DOM API(scrollIntoView、ResizeObserver 等)mock 的可靠性与清理逻辑。

Possibly related PRs

Suggested reviewers

  • zombieJ

Poem

🐇 我在草丛里轻敲键,
小框有声我才跳,
若你走神我便停,
只在专注时推你高,
提交一跃,静谧又安好 ✨

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR标题准确总结了主要变更:防止在输入框未聚焦时的自动滚动,与所有文件修改直接相关。
Linked Issues check ✅ Passed PR通过在textareaRef聚焦时才执行scrollIntoView,成功防止了未聚焦状态下的自动滚动,直接解决了#56134中报告的页面跳转问题。
Out of Scope Changes check ✅ Passed 所有代码修改都围绕防止自动滚动这一目标:添加textareaRef到上下文、在DropdownMenu中添加聚焦检查、测试验证预期行为,无超出范围的变更。
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

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f3890b1 and 5fea6da.

📒 Files selected for processing (1)
  • tests/DropdownMenu.spec.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/DropdownMenu.spec.tsx

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 @ug-hero, 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 addresses a user experience issue in the mentions component where the dropdown menu would automatically scroll its selected item into view even when the user was not actively interacting with the input field. The fix ensures that the auto-scrolling behavior is now conditional on the input's focus state, leading to a more predictable and less intrusive user interface by preventing unwanted visual shifts.

Highlights

  • Conditional Auto-Scrolling: Implemented a new mechanism to prevent the mentions dropdown menu from automatically scrolling its active item into view unless the associated textarea input is currently focused, resolving an unexpected UI behavior.
  • Contextual Ref Passing: The textareaRef is now passed through the MentionsContext to allow the DropdownMenu component to access the focus state of the underlying textarea element.
  • Enhanced Test Coverage: New test cases have been added to explicitly verify that the dropdown menu items only scroll into view when the input is focused, and crucially, do not scroll when the input is unfocused, ensuring the fix works as intended.
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.

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 prevent auto-scrolling in the mentions dropdown when the input is not focused. The current implementation has a high chance of causing a runtime error because it tries to access a nativeElement property on a ref object where it doesn't exist. I've provided a series of comments with code suggestions to refactor this using the existing isFocus state from the Mentions component, which is a more robust and safer approach. This will achieve the desired functionality correctly.

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: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/MentionsContext.ts (1)

4-15: TextAreaRef import should use import type and textareaRef type should match actual useRef return type

Two type issues need correction:

  1. Use type-only import for TextAreaRef

    -import { TextAreaRef } from '@rc-component/textarea';
    +import type { TextAreaRef } from '@rc-component/textarea';

    TextAreaRef is a type interface, not a runtime value. Using a value import can generate bundling warnings or runtime errors in strict configurations.

  2. textareaRef type declaration mismatches actual useRef behavior

    In Mentions.tsx, useRef<TextAreaRef>(null) returns React.RefObject<TextAreaRef> with a readonly, nullable current property. The current declaration as MutableRefObject<TextAreaRef> is incompatible and doesn't reflect the actual nullable current.

    export interface MentionsContextProps {
      // ...
    -  textareaRef: React.MutableRefObject<TextAreaRef>;
    +  textareaRef: React.RefObject<TextAreaRef>;
    }

    This matches the actual ref type from useRef and correctly signals to context consumers that current is readonly and may be null.

🧹 Nitpick comments (1)
tests/DropdownMenu.spec.tsx (1)

2-3: 测试用例基本覆盖了新行为,可再做少量健壮性增强

这一组改动整体很不错,验证了:

  • 文本框聚焦 + 输入触发下拉后,键盘 ArrowDown 能正确触发 scrollIntoView
  • 文本框失焦后,通过菜单 mouseEnter 改变 activeIndex 不会再触发 scrollIntoView,避免页面被强制滚动到底部。

有几处可以考虑顺手增强一下测试的健壮性(均为非必须建议):

  1. ResizeObserver mock 建议恢复原值

    beforeAll 里直接覆盖了 global.ResizeObserver,但没有在 afterAll 恢复,后续其他测试文件如果依赖真实实现,可能受影响。可以存一份原始引用并在 afterAll 里还原,例如:

    let originResizeObserver: typeof ResizeObserver;
    
    beforeAll(() => {
  • global.ResizeObserver = class ResizeObserver {
  • originResizeObserver = (global as any).ResizeObserver;
  • (global as any).ResizeObserver = class ResizeObserver {
    observe() {}
    unobserve() {}
    disconnect() {}
    };
    });

+afterAll(() => {

  • (global as any).ResizeObserver = originResizeObserver;
    +});
    
    
  1. textarea 查询最好加非空保障

    目前直接:

    const textarea = container.querySelector('textarea');
    textarea.focus();

    在 TypeScript 严格模式或未来 DOM 结构调整时容易埋雷,建议显式断言或使用 Testing Library 的查询 API,例如:

    const textarea = container.querySelector('textarea') as HTMLTextAreaElement;
    expect(textarea).toBeTruthy();

    或:

    const textarea = screen.getByRole('textbox') as HTMLTextAreaElement;
  2. ArrowDown 事件可显式携带 keyCode/which

    目前:

    fireEvent.keyDown(textarea, { key: 'ArrowDown', code: 'ArrowDown' });

    组件内部用的是 event.whichKeyCode.DOWN 比较,为了减少对测试环境实现细节的依赖,可以显式补充:

    -fireEvent.keyDown(textarea, { key: 'ArrowDown', code: 'ArrowDown' });
    +fireEvent.keyDown(textarea, {
    +  key: 'ArrowDown',
    +  code: 'ArrowDown',
    +  keyCode: 40,
    +  which: 40,
    +});

这些都不影响当前 PR 的主要目标,只是让测试在类型检查和未来依赖升级时更稳一些。

Also applies to: 12-18, 24-27, 29-58, 60-94

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9981c23 and 7274ea2.

📒 Files selected for processing (4)
  • src/DropdownMenu.tsx (3 hunks)
  • src/Mentions.tsx (1 hunks)
  • src/MentionsContext.ts (2 hunks)
  • tests/DropdownMenu.spec.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
tests/DropdownMenu.spec.tsx (1)
tests/util.ts (1)
  • simulateInput (3-24)
🔇 Additional comments (2)
src/Mentions.tsx (1)

523-533: textareaRef 下放到 MentionsContext 的做法是合理的

这里把 textareaRef 一起塞进 MentionsContext.Provider,让下游(比如 DropdownMenu)能够感知当前 textarea 的真实焦点状态,用于决定是否触发滚动,和 PR 目标高度一致,且不会改变现有公开 API 行为。逻辑本身没问题。类型细节我在 src/MentionsContext.ts 里单独提了一点建议,可一并参考。

src/DropdownMenu.tsx (1)

17-26: Verify TextAreaRef property access pattern against rc-textarea API

The focus-based scroll guard logic is conceptually sound, but the implementation depends critically on how TextAreaRef is accessed. According to rc-textarea's type definitions, the ref type exposes a resizableTextArea field rather than a nativeElement property. If the code checks document.activeElement !== textareaRef.current.nativeElement, this references an undefined property, breaking the guard entirely.

Confirm:

  • Whether the actual code uses .nativeElement (does not exist on TextAreaRef) or .resizableTextArea?.textArea (the correct path)
  • If .nativeElement is used, this is a critical compatibility issue that would make the guard ineffective and require correcting the property access pattern

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

♻️ Duplicate comments (1)
src/DropdownMenu.tsx (1)

53-53: 依赖数组应使用 isFocus 而非 textareaRef

为确保焦点状态变化时副作用能够重新运行,依赖数组应包含 isFocus 而不是 textareaRef。这与上述建议使用 isFocus 的修复方案一致。

应用此差异更新依赖数组:

-  }, [activeIndex, activeOption.key, opened, textareaRef]);
+  }, [activeIndex, activeOption.key, opened, isFocus]);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7274ea2 and b75ddd3.

📒 Files selected for processing (1)
  • src/DropdownMenu.tsx (3 hunks)

@ug-hero
Copy link
Author

ug-hero commented Dec 9, 2025

使用ref相比使用isFocus(state)可以避免不必要的重渲染,每次用户失焦/聚焦都会更新Context,导致所有消费该Context的组件(甚至整个树)重新渲染。

@codecov
Copy link

codecov bot commented Dec 9, 2025

Codecov Report

❌ Patch coverage is 75.00000% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 98.17%. Comparing base (9981c23) to head (f3890b1).

Files with missing lines Patch % Lines
src/DropdownMenu.tsx 75.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #307      +/-   ##
==========================================
- Coverage   98.51%   98.17%   -0.35%     
==========================================
  Files           8        8              
  Lines         270      274       +4     
  Branches       66       69       +3     
==========================================
+ Hits          266      269       +3     
- Misses          4        5       +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.

@afc163
Copy link
Member

afc163 commented Dec 9, 2025

覆盖率掉了

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: 0

🧹 Nitpick comments (2)
tests/DropdownMenu.spec.tsx (2)

2-3: 测试初始化整体合理,但可以更安全地处理全局 ResizeObserver 与 mocks

当前在 beforeAll 里直接覆盖 global.ResizeObserver,以及在 afterEach 里只做 jest.clearAllMocks(),对本文件是够用的,但对其他测试文件会有一些全局副作用风险。建议:

  1. beforeAll/afterAll 中保存并恢复原始的 ResizeObserver,避免污染其它用例运行环境。
  2. 如果后续会有更多 jest.spyOn,可以考虑在 afterEach 中统一使用 jest.restoreAllMocks(),这样即使某个用例中间抛错,也能保证原型方法被恢复,不需要每个用例里都手动 mockRestore()

示例调整(类型细节可以按你们的 ts 配置调整):

-  beforeAll(() => {
-    global.ResizeObserver = class ResizeObserver {
-      observe() {}
-      unobserve() {}
-      disconnect() {}
-    };
-  });
+  let originResizeObserver: any;
+  beforeAll(() => {
+    originResizeObserver = (global as any).ResizeObserver;
+    (global as any).ResizeObserver = class ResizeObserver {
+      observe() {}
+      unobserve() {}
+      disconnect() {}
+    };
+  });
+
+  afterAll(() => {
+    (global as any).ResizeObserver = originResizeObserver;
+  });
 
   afterEach(() => {
     jest.useRealTimers();
-    jest.clearAllMocks();
+    jest.restoreAllMocks();
   });

这样可以让这个测试文件对全局环境更加“自清理”。

Also applies to: 12-18, 24-27


60-90: 建议加强第二个用例的前置条件断言,避免“菜单未打开时也能通过”

这个用例很好地模拟了“输入后 blur + 鼠标移入菜单项”的场景,并断言 scrollIntoView 不应被调用,逻辑上和本次修复的 bug 非常贴合。不过这里有个小空隙:

  • 当前写法中:
    const menuItems = document.querySelectorAll('.rc-mentions-menu-item');
    if (menuItems.length > 1) {
      fireEvent.mouseEnter(menuItems[1]);
    }
    如果将来某个改动导致菜单根本没渲染出来(menuItems.length <= 1),mouseEnter 分支不会执行,此时即使 DropdownMenu 在“菜单打开”状态下又误调用了 scrollIntoView,该用例也依然可能通过。

建议:

  1. 显式断言菜单项数量,确保测试在“菜单确实打开且存在多个选项”的前提下运行。
  2. 可以顺便将查询范围限定在 container,避免未来页面上存在多组 Mentions 时串场。

示例修改:

-      const menuItems = document.querySelectorAll('.rc-mentions-menu-item');
-      if (menuItems.length > 1) {
-        fireEvent.mouseEnter(menuItems[1]);
-      }
+      const menuItems = container.querySelectorAll('.rc-mentions-menu-item');
+      expect(menuItems.length).toBeGreaterThan(1);
+      fireEvent.mouseEnter(menuItems[1]);

这样一来,如果菜单行为有任何回归(比如根本没打开),测试会明确失败,不会“假阳性”通过。

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b75ddd3 and 1aa2da4.

📒 Files selected for processing (1)
  • tests/DropdownMenu.spec.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
tests/DropdownMenu.spec.tsx (1)
tests/util.ts (1)
  • simulateInput (3-24)
🔇 Additional comments (1)
tests/DropdownMenu.spec.tsx (1)

29-58: 键盘导航 + 聚焦场景的测试覆盖良好,能有效防止回归

该用例在聚焦 textarea 后,通过 simulateInput(container, '@') 打开下拉,再发出 ArrowDown 并跑完定时器,最终断言 scrollIntoView 被带着 { block: 'nearest', inline: 'nearest' } 调用,和业务期望(仅在输入框已聚焦时允许自动滚动)对齐。整体 arrange‑act‑assert 结构清晰,能很好地覆盖“正常聚焦下滚动应发生”的正向路径。

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: 0

🧹 Nitpick comments (4)
tests/DropdownMenu.spec.tsx (4)

12-24: ResizeObserver 全局 mock 合理,注意与其他测试的交互

beforeAll/afterAll 中挂接全局 ResizeObserver,可以稳定依赖它的布局逻辑,避免 JSDOM 环境差异导致测试抖动,这里做法合理。
唯一需要留意的是:如果其他测试文件也 mock ResizeObserver,要确保它们同样在各自 afterAll 中恢复,避免相互污染;目前本文件内是对称恢复的,没有问题。

如后续有更多测试依赖 ResizeObserver,可以考虑提取一个通用的测试 helper 统一管理此 mock。


26-33: Jest 计时器与 mock 清理基本到位,可精简重复恢复

beforeEach 中启用 fake timers,afterEach 中恢复 real timers 并调用 jest.restoreAllMocks(),整体生命周期管理是健康的。

当前每个用例内还手动调用了 scrollIntoViewMock.mockRestore(),在已有 jest.restoreAllMocks() 的情况下略显多余,可以考虑删掉这些手动 restore,让清理逻辑集中在 afterEach,减少重复与潜在遗漏。

请确认你的 Jest 版本下 jest.restoreAllMocks() 能覆盖 jest.spyOn(HTMLElement.prototype, 'scrollIntoView') 的场景。


35-64: 聚焦场景测试覆盖到位,建议增加一点健壮性

这个用例很好地模拟了:textarea 聚焦 → 输入 @ 打开菜单 → 键盘 ArrowDown 导航 → 期望 scrollIntoView{ block: 'nearest', inline: 'nearest' } 被调用,和新的“仅在 textarea 聚焦时滚动”的逻辑吻合。

有两点可以考虑(都属可选增强):

  1. const textarea = container.querySelector('textarea'); 后直接使用 textarea.focus() / fireEvent.keyDown(textarea, ...),在类型上和运行时都假设不为 null。可以在断言前加一行 expect(textarea).not.toBeNull(); 并在 TS 层用 as HTMLTextAreaElement 断言,避免未来 JSX 结构调整导致空指针。
  2. 如需进一步防止多次滚动,可选加一句 expect(scrollIntoViewMock).toHaveBeenCalledTimes(1); 明确只触发一次。

请确认在当前 RTL + React 版本下,将 jest.runAllTimers() 放在 await act(async () => { ... }) 里不会触发额外的 act 警告。


66-95: 失焦但菜单仍交互时不滚动的回归用例设计合理

第二个用例很好地锁定了本 PR 的关键回归场景:

  • 聚焦 textarea 并输入 @ 打开菜单;
  • 触发 blur,再对第二个菜单项触发 mouseEnter
  • 断言菜单项仍然存在(length > 1),但 scrollIntoView 没有被调用,验证“未聚焦时走 return 提前退出”的分支。

同样有两点可以考虑的小改进:

  1. 和上一个测试类似,为 textarea 做一次非空断言 + 类型断言,提高可维护性。
  2. scrollIntoViewMock.mockRestore() 在有 jest.restoreAllMocks() 的前提下可以删除,交给统一清理。

整体上,这个测试非常贴合 issue 场景和新逻辑,能够有效防止回归。

建议在本地多跑几次带 --runInBand 和默认模式的测试,确认在不同执行顺序下此用例不会出现 flakiness。

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1aa2da4 and a48468f.

📒 Files selected for processing (1)
  • tests/DropdownMenu.spec.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
tests/DropdownMenu.spec.tsx (1)
tests/util.ts (1)
  • simulateInput (3-24)
🔇 Additional comments (1)
tests/DropdownMenu.spec.tsx (1)

2-4: 导入更新方向正确

使用 @testing-library/reactrender/act/fireEvent 并直接从 ../src 引入 Mentions,与当前测试栈和组件入口保持一致,看起来是合理收敛,不需要额外改动。

请确认当前 Jest / RTL 配置下没有关于 act 的额外警告。

@afc163
Copy link
Member

afc163 commented Dec 10, 2025

ci 挂了

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: 0

🧹 Nitpick comments (4)
tests/DropdownMenu.spec.tsx (4)

12-24: ResizeObserver mock 实现正确,可考虑抽到公共测试初始化

这段在 beforeAll/afterAll 里接管 global.ResizeObserver 的做法是安全的,也能保证测试结束后还原环境。不过如果其他测试文件也需要 ResizeObserver 的 stub,建议考虑统一放到测试环境初始化(例如 jest setup 文件或一个共享 util)里,避免多处重复实现、后续改动时容易不一致。


30-33: 全局 jest.restoreAllMocks 与每个用例里的 mockRestore 有些重复

afterEach 里已经调用了 jest.restoreAllMocks(),它会把 scrollIntoView 的 spy 一并还原,因此两处用例中额外的 scrollIntoViewMock.mockRestore() 实际上是冗余的。可以考虑只保留一种方式(更推荐保留 afterEach 里的全局恢复逻辑),让每个用例更简洁一些。

Also applies to: 63-64, 102-103


35-64: 聚焦场景的滚动用例覆盖了核心行为,建议补一个非空断言以提升健壮性

这个测试很好地模拟了“输入触发下拉 + 键盘 ArrowDown 导致滚动”的完整链路,并精确断言了 scrollIntoView 的参数,能有效锁住回归行为。

小建议:textarea 来自 querySelector('textarea'),类型上可能为 HTMLTextAreaElement | null,后续直接调用 textarea.focus()/fireEvent.keyDown,如果渲染结构变动导致找不到节点,会在运行期抛 NPE。可以在获取后立刻加一行断言/非空断言,例如:

const textarea = container.querySelector('textarea');
expect(textarea).not.toBeNull();
// 或者 const textarea = container.querySelector('textarea') as HTMLTextAreaElement;

这样在渲染结构变化时,失败信息会更直接,也更符合 TypeScript 的类型预期。


66-103: blur 场景用例能覆盖“菜单仍开但不应滚动”的逻辑,但需确认与 fake timers + waitFor 的配合是否稳定

这个用例很好地还原了“下拉菜单已渲染、hover 第二项、输入框 blur 后不应触发滚动”的场景,并通过 expect(scrollIntoViewMock).not.toHaveBeenCalled() 验证修复点,方向是对的。

有两个实现层面的小点建议确认一下:

  1. fake timers 与 waitFor 的配合
    整个 describe 使用了 jest.useFakeTimers(),而 waitFor 内部依赖定时器轮询断言。不同版本的 Jest/React Testing Library 对 fake timers 的支持略有差异,有的会自动切换为 real timers,有的则需要你手动 runAllTimers 或在 waitFor 外包一层 real timers。建议在本地确认下:

    • 如果不手动再跑一次定时器,waitFor 能否稳定通过而不出现超时?
    • 如有不稳定,可考虑:
      • 在本用例前后暂时切回 real timers,或
      • 用额外的 act(() => jest.runAllTimers()) 来驱动 waitFor 轮询。
  2. blurdocument.activeElement 的影响
    当前生产代码的逻辑是依据 document.activeElement 是否为 textarea 来决定是否调用 scrollIntoView。这里通过 fireEvent.blur(textarea) 来模拟失焦,按多数环境实现会同步更新 document.activeElement,但不同测试环境/JSDOM 版本的细节实现可能略有差别。建议在调试时确认一下用例中 blur 之后 document.activeElement 的值,确保与生产逻辑的判断条件一致。

整体来说逻辑和覆盖面都不错,只是这两个点关系到用例是否在 CI 中稳定执行,建议再验证一下以避免隐性 flakiness。

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a48468f and f3890b1.

📒 Files selected for processing (1)
  • tests/DropdownMenu.spec.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
tests/DropdownMenu.spec.tsx (1)
tests/util.ts (1)
  • simulateInput (3-24)
🔇 Additional comments (1)
tests/DropdownMenu.spec.tsx (1)

2-3: 测试依赖导入合理,覆盖了后续用到的 API

这里新增的 render/act/fireEvent/waitFor 以及直接渲染 Mentions 都与下方用例保持一致,用法和范围看起来是正确的,没有额外多余依赖。

@afc163
Copy link
Member

afc163 commented Dec 12, 2025

覆盖率掉了。

@ug-hero
Copy link
Author

ug-hero commented Dec 12, 2025

覆盖率掉了。

这个覆盖率检测我应该如何触发呢?现在改了一下,不知道如何触发

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.

进入mentions页,加载一会后,会跳到底部

2 participants