From e145d841c1f2277d51a78705216888a448199172 Mon Sep 17 00:00:00 2001 From: WB01676250 Date: Fri, 21 Feb 2025 15:00:09 +0800 Subject: [PATCH 01/20] fix:MetionsScroll --- src/DropdownMenu.tsx | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/DropdownMenu.tsx b/src/DropdownMenu.tsx index 59b5ca3..6c5265f 100644 --- a/src/DropdownMenu.tsx +++ b/src/DropdownMenu.tsx @@ -1,5 +1,5 @@ import Menu, { MenuItem } from 'rc-menu'; -import * as React from 'react'; +import React, { useEffect, useRef } from 'react'; import MentionsContext from './MentionsContext'; import type { DataDrivenOptionProps } from './Mentions'; interface DropdownMenuProps { @@ -24,15 +24,35 @@ function DropdownMenu(props: DropdownMenuProps) { const { prefixCls, options } = props; const activeOption = options[activeIndex] || {}; + const itemRefs = useRef([]); + + // Monitor the changes in ActiveIndex and scroll to the visible area if there are any changes + const scrollToActiveOption = (index: number) => { + if (index === -1 || !itemRefs.current[index]) return; + const activeRef = itemRefs.current[index]; + activeRef.scrollIntoView({ block: 'nearest', inline: 'nearest' }); + }; + + const handleSelect = (key: string) => { + const option = options.find(({ key: optionKey }) => optionKey === key); + selectOption(option); + }; + + const getMenuItemRef = (node: HTMLElement | null, index: number) => { + if (node) { + itemRefs.current[index] = node; + } + }; + + useEffect(() => { + scrollToActiveOption(activeIndex); + }, [activeIndex]); return ( { - const option = options.find(({ key: optionKey }) => optionKey === key); - selectOption(option); - }} + onSelect={({ key }) => handleSelect(key)} onFocus={onFocus} onBlur={onBlur} onScroll={onScroll} @@ -42,6 +62,7 @@ function DropdownMenu(props: DropdownMenuProps) { return ( getMenuItemRef(node, index)} disabled={disabled} className={className} style={style} From 2a20214be503595f7f08db0491c4cea5462cd9f2 Mon Sep 17 00:00:00 2001 From: WB01676250 Date: Mon, 24 Feb 2025 09:59:45 +0800 Subject: [PATCH 02/20] fix:MetionsScroll --- src/DropdownMenu.tsx | 93 ++++++++++++++++++++++---------------------- 1 file changed, 46 insertions(+), 47 deletions(-) diff --git a/src/DropdownMenu.tsx b/src/DropdownMenu.tsx index 6c5265f..e984c0e 100644 --- a/src/DropdownMenu.tsx +++ b/src/DropdownMenu.tsx @@ -24,59 +24,58 @@ function DropdownMenu(props: DropdownMenuProps) { const { prefixCls, options } = props; const activeOption = options[activeIndex] || {}; - const itemRefs = useRef([]); + const menuWrapperRef = useRef(null); // Monitor the changes in ActiveIndex and scroll to the visible area if there are any changes - const scrollToActiveOption = (index: number) => { - if (index === -1 || !itemRefs.current[index]) return; - const activeRef = itemRefs.current[index]; - activeRef.scrollIntoView({ block: 'nearest', inline: 'nearest' }); - }; - - const handleSelect = (key: string) => { - const option = options.find(({ key: optionKey }) => optionKey === key); - selectOption(option); - }; - - const getMenuItemRef = (node: HTMLElement | null, index: number) => { - if (node) { - itemRefs.current[index] = node; - } - }; - useEffect(() => { - scrollToActiveOption(activeIndex); - }, [activeIndex]); + if (activeIndex === -1 || !menuWrapperRef.current) { + return; + } + const selector = `.${prefixCls}-menu-item:nth-child(${activeIndex + 1})`; + const activeItem = menuWrapperRef.current.querySelector(selector); + if (activeItem) { + activeItem.scrollIntoView({ + block: 'nearest', + inline: 'nearest', + }); + } + }, [activeIndex, prefixCls]); return ( - handleSelect(key)} - onFocus={onFocus} - onBlur={onBlur} - onScroll={onScroll} - > - {options.map((option, index) => { - const { key, disabled, className, style, label } = option; - return ( - getMenuItemRef(node, index)} - disabled={disabled} - className={className} - style={style} - onMouseEnter={() => { - setActiveIndex(index); - }} - > - {label} - - ); - })} +
+ { + const option = options.find( + ({ key: optionKey }) => optionKey === key, + ); + selectOption(option); + }} + onFocus={onFocus} + onBlur={onBlur} + onScroll={onScroll} + > + {options.map((option, index) => { + const { key, disabled, className, style, label } = option; + return ( + { + setActiveIndex(index); + }} + > + {label} + + ); + })} - {!options.length && {notFoundContent}} - + {!options.length && {notFoundContent}} +
+ ); } From a19755f2e2bd369263188f2b3a7b351c3b34cf50 Mon Sep 17 00:00:00 2001 From: WB01676250 Date: Mon, 24 Feb 2025 11:29:45 +0800 Subject: [PATCH 03/20] fix:MetionsScroll --- docs/examples/onScroll.tsx | 2 +- src/DropdownMenu.tsx | 72 ++++++++++++++++++-------------------- 2 files changed, 35 insertions(+), 39 deletions(-) diff --git a/docs/examples/onScroll.tsx b/docs/examples/onScroll.tsx index 1274fec..d160ca5 100644 --- a/docs/examples/onScroll.tsx +++ b/docs/examples/onScroll.tsx @@ -10,7 +10,7 @@ export default () => ( onPopupScroll={console.log} dropdownClassName="on-scroll" open - options={Array.from({ length: 1000 }).map((_, index) => ({ + options={Array.from({ length: 20 }).map((_, index) => ({ value: `item-${index}`, label: `item-${index}`, }))} diff --git a/src/DropdownMenu.tsx b/src/DropdownMenu.tsx index e984c0e..ca3954a 100644 --- a/src/DropdownMenu.tsx +++ b/src/DropdownMenu.tsx @@ -1,4 +1,4 @@ -import Menu, { MenuItem } from 'rc-menu'; +import Menu, { MenuItem, MenuRef } from 'rc-menu'; import React, { useEffect, useRef } from 'react'; import MentionsContext from './MentionsContext'; import type { DataDrivenOptionProps } from './Mentions'; @@ -24,15 +24,15 @@ function DropdownMenu(props: DropdownMenuProps) { const { prefixCls, options } = props; const activeOption = options[activeIndex] || {}; - const menuWrapperRef = useRef(null); + const menuRef = useRef(); // Monitor the changes in ActiveIndex and scroll to the visible area if there are any changes useEffect(() => { - if (activeIndex === -1 || !menuWrapperRef.current) { + if (activeIndex === -1 || !menuRef.current?.list) { return; } const selector = `.${prefixCls}-menu-item:nth-child(${activeIndex + 1})`; - const activeItem = menuWrapperRef.current.querySelector(selector); + const activeItem = menuRef.current?.list.querySelector(selector); if (activeItem) { activeItem.scrollIntoView({ block: 'nearest', @@ -42,40 +42,36 @@ function DropdownMenu(props: DropdownMenuProps) { }, [activeIndex, prefixCls]); return ( -
- { - const option = options.find( - ({ key: optionKey }) => optionKey === key, - ); - selectOption(option); - }} - onFocus={onFocus} - onBlur={onBlur} - onScroll={onScroll} - > - {options.map((option, index) => { - const { key, disabled, className, style, label } = option; - return ( - { - setActiveIndex(index); - }} - > - {label} - - ); - })} - - {!options.length && {notFoundContent}} - -
+ { + const option = options.find(({ key: optionKey }) => optionKey === key); + selectOption(option); + }} + onFocus={onFocus} + onBlur={onBlur} + onScroll={onScroll} + > + {options.map((option, index) => { + const { key, disabled, className, style, label } = option; + return ( + { + setActiveIndex(index); + }} + > + {label} + + ); + })} + {!options.length && {notFoundContent}} + ); } From b6446ae0a28471416bc89089888eabf74e23e86f Mon Sep 17 00:00:00 2001 From: WB01676250 Date: Mon, 24 Feb 2025 11:30:55 +0800 Subject: [PATCH 04/20] fix:MetionsScroll --- docs/examples/onScroll.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/examples/onScroll.tsx b/docs/examples/onScroll.tsx index d160ca5..1274fec 100644 --- a/docs/examples/onScroll.tsx +++ b/docs/examples/onScroll.tsx @@ -10,7 +10,7 @@ export default () => ( onPopupScroll={console.log} dropdownClassName="on-scroll" open - options={Array.from({ length: 20 }).map((_, index) => ({ + options={Array.from({ length: 1000 }).map((_, index) => ({ value: `item-${index}`, label: `item-${index}`, }))} From e78cef6f50b81a3a623379c33b6ab2eb6970c8b8 Mon Sep 17 00:00:00 2001 From: WB01676250 Date: Mon, 24 Feb 2025 11:33:10 +0800 Subject: [PATCH 05/20] fix:MetionsScroll --- src/DropdownMenu.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/DropdownMenu.tsx b/src/DropdownMenu.tsx index ca3954a..80e41be 100644 --- a/src/DropdownMenu.tsx +++ b/src/DropdownMenu.tsx @@ -70,6 +70,7 @@ function DropdownMenu(props: DropdownMenuProps) {
); })} + {!options.length && {notFoundContent}}
); From 49db0913b678dbfeddcc24318f625f8cfd082df2 Mon Sep 17 00:00:00 2001 From: WB01676250 Date: Mon, 24 Feb 2025 11:42:52 +0800 Subject: [PATCH 06/20] fix:MetionsScroll --- src/DropdownMenu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DropdownMenu.tsx b/src/DropdownMenu.tsx index 80e41be..b39bd02 100644 --- a/src/DropdownMenu.tsx +++ b/src/DropdownMenu.tsx @@ -24,7 +24,7 @@ function DropdownMenu(props: DropdownMenuProps) { const { prefixCls, options } = props; const activeOption = options[activeIndex] || {}; - const menuRef = useRef(); + const menuRef = useRef(null); // Monitor the changes in ActiveIndex and scroll to the visible area if there are any changes useEffect(() => { From 4858fa1ecf8fb28bbecda95978f7428676c0a199 Mon Sep 17 00:00:00 2001 From: WB01676250 Date: Tue, 25 Feb 2025 11:20:02 +0800 Subject: [PATCH 07/20] fix:MetionsScroll --- src/DropdownMenu.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/DropdownMenu.tsx b/src/DropdownMenu.tsx index b39bd02..24dc0d4 100644 --- a/src/DropdownMenu.tsx +++ b/src/DropdownMenu.tsx @@ -28,18 +28,18 @@ function DropdownMenu(props: DropdownMenuProps) { // Monitor the changes in ActiveIndex and scroll to the visible area if there are any changes useEffect(() => { - if (activeIndex === -1 || !menuRef.current?.list) { + if (activeIndex === -1 || !menuRef.current) { return; } - const selector = `.${prefixCls}-menu-item:nth-child(${activeIndex + 1})`; - const activeItem = menuRef.current?.list.querySelector(selector); + const selector = `[data-menu-id="${menuRef.current?.activeItem.uuid}-${menuRef.current?.activeItem.activeKey}"]`; + const activeItem = document.querySelector(selector); if (activeItem) { activeItem.scrollIntoView({ block: 'nearest', inline: 'nearest', }); } - }, [activeIndex, prefixCls]); + }, [activeIndex]); return ( Date: Wed, 26 Feb 2025 11:14:10 +0800 Subject: [PATCH 08/20] fix:metionsScroll --- src/DropdownMenu.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/DropdownMenu.tsx b/src/DropdownMenu.tsx index 24dc0d4..47e3f41 100644 --- a/src/DropdownMenu.tsx +++ b/src/DropdownMenu.tsx @@ -31,15 +31,15 @@ function DropdownMenu(props: DropdownMenuProps) { if (activeIndex === -1 || !menuRef.current) { return; } - const selector = `[data-menu-id="${menuRef.current?.activeItem.uuid}-${menuRef.current?.activeItem.activeKey}"]`; - const activeItem = document.querySelector(selector); + + const activeItem = menuRef.current?.findItem?.({ key: activeOption.key }); if (activeItem) { activeItem.scrollIntoView({ block: 'nearest', inline: 'nearest', }); } - }, [activeIndex]); + }, [activeIndex, activeOption.key]); return ( Date: Wed, 26 Feb 2025 14:33:24 +0800 Subject: [PATCH 09/20] feat:test --- src/DropdownMenu.tsx | 2 +- tests/DropdownMenu.spec.tsx | 73 +++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 tests/DropdownMenu.spec.tsx diff --git a/src/DropdownMenu.tsx b/src/DropdownMenu.tsx index 5443190..2d53d53 100644 --- a/src/DropdownMenu.tsx +++ b/src/DropdownMenu.tsx @@ -2,7 +2,7 @@ import Menu, { MenuItem, MenuRef } from '@rc-component/menu'; import React, { useEffect, useRef } from 'react'; import MentionsContext from './MentionsContext'; import type { DataDrivenOptionProps } from './Mentions'; -interface DropdownMenuProps { +export interface DropdownMenuProps { prefixCls?: string; options: DataDrivenOptionProps[]; } diff --git a/tests/DropdownMenu.spec.tsx b/tests/DropdownMenu.spec.tsx new file mode 100644 index 0000000..37bf605 --- /dev/null +++ b/tests/DropdownMenu.spec.tsx @@ -0,0 +1,73 @@ +import React from 'react'; +import { render, act } from '@testing-library/react'; +import DropdownMenu, { DropdownMenuProps } from '../src/DropdownMenu'; +import MentionsContext from '../src/MentionsContext'; + +// Mocking scrollIntoView to prevent actual DOM manipulation +global.HTMLElement.prototype.scrollIntoView = jest.fn(); + +describe('DropdownMenu useEffect', () => { + const createMockContext = (overrides = {}) => ({ + activeIndex: -1, + setActiveIndex: jest.fn(), + selectOption: jest.fn(), + onFocus: jest.fn(), + onBlur: jest.fn(), + onScroll: jest.fn(), + notFoundContent: 'No results', + ...overrides, + }); + + const setup = ( + props: Partial = {}, + context = createMockContext(), + ) => { + return render( + + + , + ); + }; + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should scroll to active item when activeIndex changes', async () => { + const mockContext = createMockContext(); + const { rerender } = setup({}, mockContext); + + // No scroll on initial render with activeIndex -1 + expect(global.HTMLElement.prototype.scrollIntoView).not.toBeCalled(); + + await act(async () => { + // Update context activeIndex + mockContext.activeIndex = 1; + // Re-render the component + rerender( + + + , + ); + }); + + // Check that scrollIntoView was called + expect(global.HTMLElement.prototype.scrollIntoView).toHaveBeenCalledWith({ + block: 'nearest', + inline: 'nearest', + }); + }); +}); From a8d43fc7e53791ebb7155fffd60af168990a4eba Mon Sep 17 00:00:00 2001 From: WB01676250 Date: Wed, 26 Feb 2025 16:45:38 +0800 Subject: [PATCH 10/20] feat:test --- package.json | 3 ++- src/DropdownMenu.tsx | 3 ++- tests/DropdownMenu.spec.tsx | 24 ++++++++++-------------- tests/FullProcess.spec.tsx | 2 ++ tests/Mentions.spec.tsx | 2 ++ 5 files changed, 18 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index 5ec70a3..1c1803d 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,8 @@ "@rc-component/textarea": "~1.0.0", "@rc-component/trigger": "^3.0.0", "@rc-component/util": "^1.2.0", - "classnames": "^2.2.6" + "classnames": "^2.2.6", + "scroll-into-view-if-needed": "^3.1.0" }, "devDependencies": { "@rc-component/father-plugin": "^2.0.2", diff --git a/src/DropdownMenu.tsx b/src/DropdownMenu.tsx index 2d53d53..298fd4d 100644 --- a/src/DropdownMenu.tsx +++ b/src/DropdownMenu.tsx @@ -2,6 +2,7 @@ import Menu, { MenuItem, MenuRef } from '@rc-component/menu'; import React, { useEffect, useRef } from 'react'; import MentionsContext from './MentionsContext'; import type { DataDrivenOptionProps } from './Mentions'; +import scrollIntoView from 'scroll-into-view-if-needed'; export interface DropdownMenuProps { prefixCls?: string; options: DataDrivenOptionProps[]; @@ -34,7 +35,7 @@ function DropdownMenu(props: DropdownMenuProps) { const activeItem = menuRef.current?.findItem?.({ key: activeOption.key }); if (activeItem) { - activeItem.scrollIntoView({ + scrollIntoView(activeItem, { block: 'nearest', inline: 'nearest', }); diff --git a/tests/DropdownMenu.spec.tsx b/tests/DropdownMenu.spec.tsx index 37bf605..db7a3f9 100644 --- a/tests/DropdownMenu.spec.tsx +++ b/tests/DropdownMenu.spec.tsx @@ -2,9 +2,9 @@ import React from 'react'; import { render, act } from '@testing-library/react'; import DropdownMenu, { DropdownMenuProps } from '../src/DropdownMenu'; import MentionsContext from '../src/MentionsContext'; +import scrollIntoView from 'scroll-into-view-if-needed'; -// Mocking scrollIntoView to prevent actual DOM manipulation -global.HTMLElement.prototype.scrollIntoView = jest.fn(); +jest.mock('scroll-into-view-if-needed'); describe('DropdownMenu useEffect', () => { const createMockContext = (overrides = {}) => ({ @@ -25,7 +25,7 @@ describe('DropdownMenu useEffect', () => { return render( { ); }; - afterEach(() => { - jest.clearAllMocks(); - }); - it('should scroll to active item when activeIndex changes', async () => { const mockContext = createMockContext(); const { rerender } = setup({}, mockContext); - // No scroll on initial render with activeIndex -1 - expect(global.HTMLElement.prototype.scrollIntoView).not.toBeCalled(); + expect(scrollIntoView).not.toHaveBeenCalled(); await act(async () => { - // Update context activeIndex mockContext.activeIndex = 1; - // Re-render the component + rerender( { ); }); - // Check that scrollIntoView was called - expect(global.HTMLElement.prototype.scrollIntoView).toHaveBeenCalledWith({ + const activeItemNode = document.querySelector( + '.rc-mentions-dropdown-menu-item-active', + ); + expect(scrollIntoView).toHaveBeenCalledWith(activeItemNode, { block: 'nearest', inline: 'nearest', }); diff --git a/tests/FullProcess.spec.tsx b/tests/FullProcess.spec.tsx index 61ebbb3..58b690a 100644 --- a/tests/FullProcess.spec.tsx +++ b/tests/FullProcess.spec.tsx @@ -5,6 +5,8 @@ import type { MentionsProps } from '../src'; import Mentions from '../src'; import { expectMatchOptions, expectMeasuring, simulateInput } from './util'; +jest.mock('scroll-into-view-if-needed'); + describe('Full Process', () => { function createMentions(props?: MentionsProps) { return render( diff --git a/tests/Mentions.spec.tsx b/tests/Mentions.spec.tsx index 25146d0..b6ef7a4 100644 --- a/tests/Mentions.spec.tsx +++ b/tests/Mentions.spec.tsx @@ -9,6 +9,8 @@ import { expectMatchOptions, expectMeasuring, simulateInput } from './util'; const { Option } = Mentions; +jest.mock('scroll-into-view-if-needed'); + describe('Mentions', () => { function createMentions( props?: MentionsProps & { ref?: React.Ref }, From 40acc30e38bdcd3cae6ffd9eb2e533b70eb1c816 Mon Sep 17 00:00:00 2001 From: WB01676250 Date: Wed, 26 Feb 2025 18:16:27 +0800 Subject: [PATCH 11/20] fix:test --- src/DropdownMenu.tsx | 1 + tests/DropdownMenu.spec.tsx | 19 +++++++++++++++---- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/DropdownMenu.tsx b/src/DropdownMenu.tsx index 298fd4d..1c4fbb4 100644 --- a/src/DropdownMenu.tsx +++ b/src/DropdownMenu.tsx @@ -38,6 +38,7 @@ function DropdownMenu(props: DropdownMenuProps) { scrollIntoView(activeItem, { block: 'nearest', inline: 'nearest', + scrollMode: 'if-needed', }); } }, [activeIndex, activeOption.key]); diff --git a/tests/DropdownMenu.spec.tsx b/tests/DropdownMenu.spec.tsx index db7a3f9..6f11ec1 100644 --- a/tests/DropdownMenu.spec.tsx +++ b/tests/DropdownMenu.spec.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { render, act } from '@testing-library/react'; +import { render, act, screen } from '@testing-library/react'; import DropdownMenu, { DropdownMenuProps } from '../src/DropdownMenu'; import MentionsContext from '../src/MentionsContext'; import scrollIntoView from 'scroll-into-view-if-needed'; @@ -18,6 +18,10 @@ describe('DropdownMenu useEffect', () => { ...overrides, }); + afterEach(() => { + jest.clearAllMocks(); + }); + const setup = ( props: Partial = {}, context = createMockContext(), @@ -44,7 +48,6 @@ describe('DropdownMenu useEffect', () => { await act(async () => { mockContext.activeIndex = 1; - rerender( { ); }); - const activeItemNode = document.querySelector( - '.rc-mentions-dropdown-menu-item-active', + const menuItems = await screen.findAllByRole('menuitem'); + + const activeItemNode = menuItems.find( + item => + item.textContent === 'Option 2' && + item.classList.contains('rc-mentions-dropdown-menu-item-active'), ); + + expect(scrollIntoView).toHaveBeenCalled(); + expect(scrollIntoView).toHaveBeenCalledWith(activeItemNode, { block: 'nearest', inline: 'nearest', + scrollMode: 'if-needed', }); }); }); From 038db6830165dd456581d8cba6df67a40288ee55 Mon Sep 17 00:00:00 2001 From: jinle <986530572@qq.com> Date: Wed, 26 Feb 2025 19:23:43 +0800 Subject: [PATCH 12/20] fix:test --- tests/AllowClear.spec.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/AllowClear.spec.tsx b/tests/AllowClear.spec.tsx index 43e4bfa..dddf73e 100644 --- a/tests/AllowClear.spec.tsx +++ b/tests/AllowClear.spec.tsx @@ -2,6 +2,8 @@ import { fireEvent, render } from '@testing-library/react'; import React from 'react'; import Mentions from '../src'; +jest.mock('scroll-into-view-if-needed'); + describe('should support allowClear', () => { it('should change type when click', () => { const { container } = render(); From 31e523e7da0bb27e1a57be30f784bfe6cd3f9afe Mon Sep 17 00:00:00 2001 From: WB01676250 Date: Thu, 27 Feb 2025 11:18:46 +0800 Subject: [PATCH 13/20] fix:test --- package.json | 3 +- src/DropdownMenu.tsx | 6 +-- tests/DropdownMenu.spec.tsx | 80 ------------------------------------- tests/FullProcess.spec.tsx | 2 - tests/Mentions.spec.tsx | 2 - 5 files changed, 3 insertions(+), 90 deletions(-) delete mode 100644 tests/DropdownMenu.spec.tsx diff --git a/package.json b/package.json index d74153b..d7d7b7f 100644 --- a/package.json +++ b/package.json @@ -50,8 +50,7 @@ "@rc-component/textarea": "~1.0.0", "@rc-component/trigger": "^3.0.0", "@rc-component/util": "^1.2.0", - "classnames": "^2.2.6", - "scroll-into-view-if-needed": "^3.1.0" + "classnames": "^2.2.6" }, "devDependencies": { "@rc-component/father-plugin": "^2.0.2", diff --git a/src/DropdownMenu.tsx b/src/DropdownMenu.tsx index 1c4fbb4..5443190 100644 --- a/src/DropdownMenu.tsx +++ b/src/DropdownMenu.tsx @@ -2,8 +2,7 @@ import Menu, { MenuItem, MenuRef } from '@rc-component/menu'; import React, { useEffect, useRef } from 'react'; import MentionsContext from './MentionsContext'; import type { DataDrivenOptionProps } from './Mentions'; -import scrollIntoView from 'scroll-into-view-if-needed'; -export interface DropdownMenuProps { +interface DropdownMenuProps { prefixCls?: string; options: DataDrivenOptionProps[]; } @@ -35,10 +34,9 @@ function DropdownMenu(props: DropdownMenuProps) { const activeItem = menuRef.current?.findItem?.({ key: activeOption.key }); if (activeItem) { - scrollIntoView(activeItem, { + activeItem.scrollIntoView({ block: 'nearest', inline: 'nearest', - scrollMode: 'if-needed', }); } }, [activeIndex, activeOption.key]); diff --git a/tests/DropdownMenu.spec.tsx b/tests/DropdownMenu.spec.tsx deleted file mode 100644 index 6f11ec1..0000000 --- a/tests/DropdownMenu.spec.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import React from 'react'; -import { render, act, screen } from '@testing-library/react'; -import DropdownMenu, { DropdownMenuProps } from '../src/DropdownMenu'; -import MentionsContext from '../src/MentionsContext'; -import scrollIntoView from 'scroll-into-view-if-needed'; - -jest.mock('scroll-into-view-if-needed'); - -describe('DropdownMenu useEffect', () => { - const createMockContext = (overrides = {}) => ({ - activeIndex: -1, - setActiveIndex: jest.fn(), - selectOption: jest.fn(), - onFocus: jest.fn(), - onBlur: jest.fn(), - onScroll: jest.fn(), - notFoundContent: 'No results', - ...overrides, - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - const setup = ( - props: Partial = {}, - context = createMockContext(), - ) => { - return render( - - - , - ); - }; - - it('should scroll to active item when activeIndex changes', async () => { - const mockContext = createMockContext(); - const { rerender } = setup({}, mockContext); - - expect(scrollIntoView).not.toHaveBeenCalled(); - - await act(async () => { - mockContext.activeIndex = 1; - rerender( - - - , - ); - }); - - const menuItems = await screen.findAllByRole('menuitem'); - - const activeItemNode = menuItems.find( - item => - item.textContent === 'Option 2' && - item.classList.contains('rc-mentions-dropdown-menu-item-active'), - ); - - expect(scrollIntoView).toHaveBeenCalled(); - - expect(scrollIntoView).toHaveBeenCalledWith(activeItemNode, { - block: 'nearest', - inline: 'nearest', - scrollMode: 'if-needed', - }); - }); -}); diff --git a/tests/FullProcess.spec.tsx b/tests/FullProcess.spec.tsx index 58b690a..61ebbb3 100644 --- a/tests/FullProcess.spec.tsx +++ b/tests/FullProcess.spec.tsx @@ -5,8 +5,6 @@ import type { MentionsProps } from '../src'; import Mentions from '../src'; import { expectMatchOptions, expectMeasuring, simulateInput } from './util'; -jest.mock('scroll-into-view-if-needed'); - describe('Full Process', () => { function createMentions(props?: MentionsProps) { return render( diff --git a/tests/Mentions.spec.tsx b/tests/Mentions.spec.tsx index 09a89c0..cdee80c 100644 --- a/tests/Mentions.spec.tsx +++ b/tests/Mentions.spec.tsx @@ -9,8 +9,6 @@ import { expectMatchOptions, expectMeasuring, simulateInput } from './util'; const { Option } = Mentions; -jest.mock('scroll-into-view-if-needed'); - describe('Mentions', () => { function createMentions( props?: MentionsProps & { ref?: React.Ref }, From d492ceb21170c8641f9c22df2897d1a4d138e24b Mon Sep 17 00:00:00 2001 From: WB01676250 Date: Thu, 27 Feb 2025 12:59:25 +0800 Subject: [PATCH 14/20] fix:test --- src/DropdownMenu.tsx | 2 +- tests/DropdownMenu.spec.tsx | 81 +++++++++++++++++++++++++++++++++++++ tests/FullProcess.spec.tsx | 2 + tests/Mentions.spec.tsx | 2 + tests/Open.spec.tsx | 2 + 5 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 tests/DropdownMenu.spec.tsx diff --git a/src/DropdownMenu.tsx b/src/DropdownMenu.tsx index 5443190..2d53d53 100644 --- a/src/DropdownMenu.tsx +++ b/src/DropdownMenu.tsx @@ -2,7 +2,7 @@ import Menu, { MenuItem, MenuRef } from '@rc-component/menu'; import React, { useEffect, useRef } from 'react'; import MentionsContext from './MentionsContext'; import type { DataDrivenOptionProps } from './Mentions'; -interface DropdownMenuProps { +export interface DropdownMenuProps { prefixCls?: string; options: DataDrivenOptionProps[]; } diff --git a/tests/DropdownMenu.spec.tsx b/tests/DropdownMenu.spec.tsx new file mode 100644 index 0000000..5bd3152 --- /dev/null +++ b/tests/DropdownMenu.spec.tsx @@ -0,0 +1,81 @@ +import React from 'react'; +import { render, act, screen } from '@testing-library/react'; +import DropdownMenu, { DropdownMenuProps } from '../src/DropdownMenu'; +import MentionsContext from '../src/MentionsContext'; + +global.HTMLElement.prototype.scrollIntoView = jest.fn(); + +describe('DropdownMenu useEffect', () => { + const createMockContext = (overrides = {}) => ({ + activeIndex: -1, + setActiveIndex: jest.fn(), + selectOption: jest.fn(), + onFocus: jest.fn(), + onBlur: jest.fn(), + onScroll: jest.fn(), + notFoundContent: 'No results', + ...overrides, + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + const setup = ( + props: Partial = {}, + context = createMockContext(), + ) => { + return render( + + + , + ); + }; + + it('should scroll to active item when activeIndex changes', async () => { + const scrollIntoViewMock = jest.fn(); + global.HTMLElement.prototype.scrollIntoView = scrollIntoViewMock; + + const mockContext = createMockContext(); + const { rerender } = setup({}, mockContext); + + // Update active index + await act(async () => { + mockContext.activeIndex = 1; // Change to the second option + rerender( + + + , + ); + }); + + // Find all menu items + const menuItems = await screen.findAllByRole('menuitem'); + + // Check if the active item is the second one + const activeItemNode = menuItems.find( + item => + item.textContent === 'Option 2' && + item.classList.contains('rc-mentions-dropdown-menu-item-active'), + ); + + expect(activeItemNode.scrollIntoView).toHaveBeenCalledTimes(1); + expect(activeItemNode.scrollIntoView).toHaveBeenCalledWith({ + block: 'nearest', + inline: 'nearest', + }); + }); +}); diff --git a/tests/FullProcess.spec.tsx b/tests/FullProcess.spec.tsx index 61ebbb3..5d46be7 100644 --- a/tests/FullProcess.spec.tsx +++ b/tests/FullProcess.spec.tsx @@ -5,6 +5,8 @@ import type { MentionsProps } from '../src'; import Mentions from '../src'; import { expectMatchOptions, expectMeasuring, simulateInput } from './util'; +global.HTMLElement.prototype.scrollIntoView = jest.fn(); + describe('Full Process', () => { function createMentions(props?: MentionsProps) { return render( diff --git a/tests/Mentions.spec.tsx b/tests/Mentions.spec.tsx index cdee80c..158c39f 100644 --- a/tests/Mentions.spec.tsx +++ b/tests/Mentions.spec.tsx @@ -9,6 +9,8 @@ import { expectMatchOptions, expectMeasuring, simulateInput } from './util'; const { Option } = Mentions; +global.HTMLElement.prototype.scrollIntoView = jest.fn(); + describe('Mentions', () => { function createMentions( props?: MentionsProps & { ref?: React.Ref }, diff --git a/tests/Open.spec.tsx b/tests/Open.spec.tsx index 3f5d634..85e5c57 100644 --- a/tests/Open.spec.tsx +++ b/tests/Open.spec.tsx @@ -3,6 +3,8 @@ import Mentions from '../src'; import { expectMeasuring } from './util'; import { render } from '@testing-library/react'; +global.HTMLElement.prototype.scrollIntoView = jest.fn(); + describe('Mentions.Open', () => { it('force open', () => { const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); From 1da64aca63ac359d12d0cd8f8b55746f3c2a57c7 Mon Sep 17 00:00:00 2001 From: WB01676250 Date: Fri, 28 Feb 2025 16:27:29 +0800 Subject: [PATCH 15/20] feat:test --- package.json | 1 + tests/DropdownMenu.spec.tsx | 9 +++++---- tests/FullProcess.spec.tsx | 2 -- tests/Mentions.spec.tsx | 2 -- tests/Open.spec.tsx | 2 -- tests/setup.ts | 2 ++ 6 files changed, 8 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index d7d7b7f..cb87b55 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ ] }, "dependencies": { + "@jest/core": "^29.7.0", "@rc-component/input": "~1.0.1", "@rc-component/menu": "~1.0.0", "@rc-component/textarea": "~1.0.0", diff --git a/tests/DropdownMenu.spec.tsx b/tests/DropdownMenu.spec.tsx index 5bd3152..f710f63 100644 --- a/tests/DropdownMenu.spec.tsx +++ b/tests/DropdownMenu.spec.tsx @@ -3,8 +3,6 @@ import { render, act, screen } from '@testing-library/react'; import DropdownMenu, { DropdownMenuProps } from '../src/DropdownMenu'; import MentionsContext from '../src/MentionsContext'; -global.HTMLElement.prototype.scrollIntoView = jest.fn(); - describe('DropdownMenu useEffect', () => { const createMockContext = (overrides = {}) => ({ activeIndex: -1, @@ -19,6 +17,7 @@ describe('DropdownMenu useEffect', () => { afterEach(() => { jest.clearAllMocks(); + jest.restoreAllMocks(); // Restore original implementations }); const setup = ( @@ -40,8 +39,10 @@ describe('DropdownMenu useEffect', () => { }; it('should scroll to active item when activeIndex changes', async () => { - const scrollIntoViewMock = jest.fn(); - global.HTMLElement.prototype.scrollIntoView = scrollIntoViewMock; + // Create the spy and mock the scrollIntoView method + const scrollIntoViewMock = jest + .spyOn(HTMLElement.prototype, 'scrollIntoView') + .mockReset(); const mockContext = createMockContext(); const { rerender } = setup({}, mockContext); diff --git a/tests/FullProcess.spec.tsx b/tests/FullProcess.spec.tsx index 5d46be7..61ebbb3 100644 --- a/tests/FullProcess.spec.tsx +++ b/tests/FullProcess.spec.tsx @@ -5,8 +5,6 @@ import type { MentionsProps } from '../src'; import Mentions from '../src'; import { expectMatchOptions, expectMeasuring, simulateInput } from './util'; -global.HTMLElement.prototype.scrollIntoView = jest.fn(); - describe('Full Process', () => { function createMentions(props?: MentionsProps) { return render( diff --git a/tests/Mentions.spec.tsx b/tests/Mentions.spec.tsx index 158c39f..cdee80c 100644 --- a/tests/Mentions.spec.tsx +++ b/tests/Mentions.spec.tsx @@ -9,8 +9,6 @@ import { expectMatchOptions, expectMeasuring, simulateInput } from './util'; const { Option } = Mentions; -global.HTMLElement.prototype.scrollIntoView = jest.fn(); - describe('Mentions', () => { function createMentions( props?: MentionsProps & { ref?: React.Ref }, diff --git a/tests/Open.spec.tsx b/tests/Open.spec.tsx index 85e5c57..3f5d634 100644 --- a/tests/Open.spec.tsx +++ b/tests/Open.spec.tsx @@ -3,8 +3,6 @@ import Mentions from '../src'; import { expectMeasuring } from './util'; import { render } from '@testing-library/react'; -global.HTMLElement.prototype.scrollIntoView = jest.fn(); - describe('Mentions.Open', () => { it('force open', () => { const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); diff --git a/tests/setup.ts b/tests/setup.ts index d44227c..35235ae 100644 --- a/tests/setup.ts +++ b/tests/setup.ts @@ -4,3 +4,5 @@ global.ResizeObserver = class ResizeObserver { unobserve() {} disconnect() {} }; + +window.HTMLElement.prototype.scrollIntoView = jest.fn(); From 63b49ae5ddf334ad0cdc4913dad3d13ef2698bc4 Mon Sep 17 00:00:00 2001 From: WB01676250 Date: Fri, 28 Feb 2025 16:29:19 +0800 Subject: [PATCH 16/20] feat:test --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index cb87b55..d7d7b7f 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,6 @@ ] }, "dependencies": { - "@jest/core": "^29.7.0", "@rc-component/input": "~1.0.1", "@rc-component/menu": "~1.0.0", "@rc-component/textarea": "~1.0.0", From cebb9c51203e09c674e87bbb229f3ee0aace8d99 Mon Sep 17 00:00:00 2001 From: WB01676250 Date: Fri, 28 Feb 2025 16:33:37 +0800 Subject: [PATCH 17/20] feat:test --- tests/DropdownMenu.spec.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/DropdownMenu.spec.tsx b/tests/DropdownMenu.spec.tsx index f710f63..3ca4547 100644 --- a/tests/DropdownMenu.spec.tsx +++ b/tests/DropdownMenu.spec.tsx @@ -40,9 +40,10 @@ describe('DropdownMenu useEffect', () => { it('should scroll to active item when activeIndex changes', async () => { // Create the spy and mock the scrollIntoView method - const scrollIntoViewMock = jest - .spyOn(HTMLElement.prototype, 'scrollIntoView') - .mockReset(); + const scrollIntoViewMock = jest.spyOn( + HTMLElement.prototype, + 'scrollIntoView', + ); const mockContext = createMockContext(); const { rerender } = setup({}, mockContext); @@ -78,5 +79,8 @@ describe('DropdownMenu useEffect', () => { block: 'nearest', inline: 'nearest', }); + + scrollIntoViewMock.mockReset(); + scrollIntoViewMock.mockRestore(); }); }); From e48cb34a93070adf5a4313fb2db2400dd28af15d Mon Sep 17 00:00:00 2001 From: WB01676250 Date: Fri, 28 Feb 2025 17:30:50 +0800 Subject: [PATCH 18/20] feat:test --- tests/DropdownMenu.spec.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/DropdownMenu.spec.tsx b/tests/DropdownMenu.spec.tsx index 92b6182..4e76749 100644 --- a/tests/DropdownMenu.spec.tsx +++ b/tests/DropdownMenu.spec.tsx @@ -40,10 +40,10 @@ describe('Mentions Component', () => { const menuItems = await screen.findAllByRole('menuitem'); // Simulate mouse enter on the second option to change active index - fireEvent.mouseEnter(menuItems[1]); + fireEvent.mouseEnter(menuItems[2]); // Verify scrollIntoView was called correctly - expect(scrollIntoViewMock).toHaveBeenCalledTimes(1); + expect(scrollIntoViewMock).toHaveBeenCalledTimes(2); expect(scrollIntoViewMock).toHaveBeenCalledWith({ block: 'nearest', inline: 'nearest', From d682701f20066411436b339bcc00d58fe94f6132 Mon Sep 17 00:00:00 2001 From: jinle <986530572@qq.com> Date: Sun, 2 Mar 2025 16:59:32 +0800 Subject: [PATCH 19/20] fix:test --- tests/DropdownMenu.spec.tsx | 72 ++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 38 deletions(-) diff --git a/tests/DropdownMenu.spec.tsx b/tests/DropdownMenu.spec.tsx index 4e76749..eda3686 100644 --- a/tests/DropdownMenu.spec.tsx +++ b/tests/DropdownMenu.spec.tsx @@ -1,57 +1,53 @@ import React from 'react'; -import { render, act, fireEvent, screen } from '@testing-library/react'; -import Mentions, { UnstableContext } from '../src'; // 修改为 Mentions 组件的实际路径 -import { expectMeasuring } from './util'; // 假定该模块中有检测测量的函数 - -describe('Mentions Component', () => { - afterEach(() => { - jest.clearAllMocks(); - jest.restoreAllMocks(); - }); - - it('should call scrollIntoView when activeIndex changes', async () => { - const options = [ - { value: 'light', label: 'Light' }, - { value: 'bamboo', label: 'Bamboo' }, - { value: 'cat', label: 'Cat' }, - ]; +import { render, fireEvent } from '@testing-library/react'; +import Mentions, { UnstableContext } from '../src'; + +describe('DropdownMenu', () => { + // Generate 20 options for testing scrolling behavior + const generateOptions = Array.from({ length: 20 }).map((_, index) => ({ + value: `item-${index}`, + label: `item-${index}`, + })); + + // Setup component with UnstableContext for testing dropdown behavior + const setup = () => { + return render( + + + , + ); + }; - // Mock the scrollIntoView method before rendering + it('should scroll into view when navigating with keyboard', () => { + // Mock scrollIntoView since it's not implemented in JSDOM const scrollIntoViewMock = jest .spyOn(HTMLElement.prototype, 'scrollIntoView') .mockImplementation(jest.fn()); - // Render Mentions with open context - const { container } = render( - - - , - ); + setup(); - // Simulate input to trigger the opening of the mentions dropdown - const textarea = container.querySelector('textarea'); - fireEvent.change(textarea, { target: { value: '@b' } }); + // Press ArrowDown multiple times to make options overflow the visible area + for (let i = 0; i < 10; i++) { + fireEvent.keyDown(document.activeElement!, { key: 'ArrowDown' }); + } - await act(async () => { - jest.runAllTimers(); // Handle any timing-related effects if applicable + // Verify if scrollIntoView was called + expect(scrollIntoViewMock).toHaveBeenCalledWith({ + block: 'nearest', + inline: 'nearest', }); - // Update the active index to simulate selection - const menuItems = await screen.findAllByRole('menuitem'); - - // Simulate mouse enter on the second option to change active index - fireEvent.mouseEnter(menuItems[2]); + // Press ArrowUp to verify scrolling up + for (let i = 0; i < 5; i++) { + fireEvent.keyDown(document.activeElement!, { key: 'ArrowUp' }); + } - // Verify scrollIntoView was called correctly - expect(scrollIntoViewMock).toHaveBeenCalledTimes(2); + // Verify if scrollIntoView was called again expect(scrollIntoViewMock).toHaveBeenCalledWith({ block: 'nearest', inline: 'nearest', }); - expectMeasuring(container); // Verify measuring after interactions if necessary - - // Clean up scrollIntoViewMock.mockReset(); scrollIntoViewMock.mockRestore(); }); From a27e2407ba701b6aa85759487b4cf1c21a98915a Mon Sep 17 00:00:00 2001 From: jinle <986530572@qq.com> Date: Sun, 2 Mar 2025 18:49:48 +0800 Subject: [PATCH 20/20] fix:test --- tests/DropdownMenu.spec.tsx | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/tests/DropdownMenu.spec.tsx b/tests/DropdownMenu.spec.tsx index eda3686..9f45684 100644 --- a/tests/DropdownMenu.spec.tsx +++ b/tests/DropdownMenu.spec.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { render, fireEvent } from '@testing-library/react'; import Mentions, { UnstableContext } from '../src'; +import { expectMeasuring } from './util'; describe('DropdownMenu', () => { // Generate 20 options for testing scrolling behavior @@ -10,13 +11,11 @@ describe('DropdownMenu', () => { })); // Setup component with UnstableContext for testing dropdown behavior - const setup = () => { - return render( - - - , - ); - }; + const { container } = render( + + + , + ); it('should scroll into view when navigating with keyboard', () => { // Mock scrollIntoView since it's not implemented in JSDOM @@ -24,8 +23,6 @@ describe('DropdownMenu', () => { .spyOn(HTMLElement.prototype, 'scrollIntoView') .mockImplementation(jest.fn()); - setup(); - // Press ArrowDown multiple times to make options overflow the visible area for (let i = 0; i < 10; i++) { fireEvent.keyDown(document.activeElement!, { key: 'ArrowDown' }); @@ -48,7 +45,8 @@ describe('DropdownMenu', () => { inline: 'nearest', }); - scrollIntoViewMock.mockReset(); + expectMeasuring(container); + scrollIntoViewMock.mockRestore(); }); });