diff --git a/.fatherrc.js b/.fatherrc.js deleted file mode 100644 index 912aa0a..0000000 --- a/.fatherrc.js +++ /dev/null @@ -1,9 +0,0 @@ -export default { - cjs: 'babel', - esm: { type: 'babel', importLibToEs: true }, - preCommit: { - eslint: true, - prettier: true, - }, - runtimeHelpers: true, -}; diff --git a/.fatherrc.ts b/.fatherrc.ts new file mode 100644 index 0000000..a5e7c0d --- /dev/null +++ b/.fatherrc.ts @@ -0,0 +1,5 @@ +import { defineConfig } from "father"; + +export default defineConfig({ + plugins: ["@rc-component/father-plugin"], +}); diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..77778f6 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,8 @@ +{ + "endOfLine": "lf", + "semi": true, + "singleQuote": true, + "tabWidth": 2, + "trailingComma": "all", + "proseWrap": "never" +} diff --git a/package.json b/package.json index 679c931..1203ed0 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "rc-util": "^5.17.0" }, "devDependencies": { + "@rc-component/father-plugin": "^1.0.0", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^14.0.0", "@types/classnames": "^2.2.6", @@ -59,7 +60,7 @@ "@types/warning": "^3.0.0", "cross-env": "^7.0.0", "dumi": "^1.1.38", - "father": "^2.13.2", + "father": "^4.0.0", "husky": "^8.0.3", "jest-environment-jsdom": "^29.5.0", "jquery": "^3.3.1", diff --git a/src/Dropdown.tsx b/src/Dropdown.tsx index 1dc64fc..fec2121 100644 --- a/src/Dropdown.tsx +++ b/src/Dropdown.tsx @@ -1,18 +1,18 @@ -import * as React from 'react'; -import Trigger from '@rc-component/trigger'; import type { TriggerProps } from '@rc-component/trigger'; -import classNames from 'classnames'; +import Trigger from '@rc-component/trigger'; import type { - AnimationType, + ActionType, AlignType, + AnimationType, BuildInPlacements, - ActionType, } from '@rc-component/trigger/lib/interface'; -import Placements from './placements'; +import classNames from 'classnames'; +import { composeRef, supportRef } from 'rc-util/lib/ref'; +import type { ReactElement } from 'react'; +import React from 'react'; import useAccessibility from './hooks/useAccessibility'; import Overlay from './Overlay'; -import { composeRef, supportRef } from 'rc-util/lib/ref'; -import { ReactElement } from 'react'; +import Placements from './placements'; export interface DropdownProps extends Pick< @@ -99,7 +99,14 @@ function Dropdown(props: DropdownProps, ref) { } }; - const getMenuElement = () => + const getMenuElement = () => ( + + ); const getMenuElementOrLambda = () => { if (typeof overlay === 'function') { @@ -126,9 +133,17 @@ function Dropdown(props: DropdownProps, ref) { }; const childrenNode = React.cloneElement(children, { - className: classNames(children.props?.className, mergedVisible && getOpenClassName()), - ref: supportRef(children) ? composeRef(childRef, (children as ReactElement & {ref: React.Ref}).ref) : undefined, - }) + className: classNames( + children.props?.className, + mergedVisible && getOpenClassName(), + ), + ref: supportRef(children) + ? composeRef( + childRef, + (children as ReactElement & { ref: React.Ref }).ref, + ) + : undefined, + }); let triggerHideAction = hideAction; if (!triggerHideAction && trigger.indexOf('contextMenu') !== -1) { diff --git a/tests/basic.test.tsx b/tests/basic.test.tsx index e4d8c36..2949961 100644 --- a/tests/basic.test.tsx +++ b/tests/basic.test.tsx @@ -1,17 +1,14 @@ /* eslint-disable react/button-has-type,react/no-find-dom-node,react/no-render-return-value,object-shorthand,func-names,max-len */ -import { act, fireEvent } from "@testing-library/react"; -import Menu, { Divider, Item as MenuItem, MenuRef } from "rc-menu"; -import { _rs } from "rc-resize-observer"; -import { spyElementPrototypes } from "rc-util/lib/test/domHook"; -import * as React from "react"; -import { - createRef, - forwardRef, - HTMLAttributes, - useImperativeHandle, -} from "react"; -import Dropdown from "../src"; -import { render, sleep } from "./utils"; +import { act, fireEvent } from '@testing-library/react'; +import type { MenuRef } from 'rc-menu'; +import Menu, { Divider, Item as MenuItem } from 'rc-menu'; +import { _rs } from 'rc-resize-observer'; +import { spyElementPrototypes } from 'rc-util/lib/test/domHook'; +import type { HTMLAttributes } from 'react'; +import * as React from 'react'; +import { createRef, forwardRef, useImperativeHandle } from 'react'; +import Dropdown from '../src'; +import { render, sleep } from './utils'; // Fix prettier rm this console.log(!!React); @@ -63,49 +60,49 @@ spyElementPrototypes(HTMLElement, { }), }); -describe("dropdown", () => { +describe('dropdown', () => { beforeEach(() => { jest.clearAllTimers(); }); - it("default visible", () => { + it('default visible', () => { const { container } = render( Test} visible> - + , ); expect(container instanceof HTMLDivElement).toBeTruthy(); expect( container - .querySelector(".my-button") - ?.classList.contains("rc-dropdown-open") + .querySelector('.my-button') + ?.classList.contains('rc-dropdown-open'), ).toBeTruthy(); }); - it("supports controlled visible prop", () => { + it('supports controlled visible prop', () => { const onVisibleChange = jest.fn(); const { container } = render( Test} visible - trigger={["click"]} + trigger={['click']} onVisibleChange={onVisibleChange} > - + , ); expect(container instanceof HTMLDivElement).toBeTruthy(); expect( container - .querySelector(".my-button") - ?.classList.contains("rc-dropdown-open") + .querySelector('.my-button') + ?.classList.contains('rc-dropdown-open'), ).toBeTruthy(); - fireEvent.click(container.querySelector(".my-button")); + fireEvent.click(container.querySelector('.my-button')); expect(onVisibleChange).toHaveBeenCalledWith(false); }); - it("simply works", async () => { + it('simply works', async () => { let clicked; function onClick({ key }) { @@ -125,37 +122,37 @@ describe("dropdown", () => { ); const { container, baseElement } = render( - + , ); - expect(container.querySelector(".my-button")).toBeTruthy(); + expect(container.querySelector('.my-button')).toBeTruthy(); // should not display until be triggered - expect(baseElement.querySelector(".rc-dropdown")).toBeFalsy(); + expect(baseElement.querySelector('.rc-dropdown')).toBeFalsy(); - fireEvent.click(container.querySelector(".my-button")); + fireEvent.click(container.querySelector('.my-button')); expect(clicked).toBeUndefined(); expect( baseElement - .querySelector(".rc-dropdown") - .classList.contains("rc-dropdown-hidden") + .querySelector('.rc-dropdown') + .classList.contains('rc-dropdown-hidden'), ).toBeFalsy(); expect(container).toMatchSnapshot(); - fireEvent.click(baseElement.querySelector(".my-menuitem")); - expect(clicked).toBe("1"); + fireEvent.click(baseElement.querySelector('.my-menuitem')); + expect(clicked).toBe('1'); expect(onOverlayClick).toHaveBeenCalled(); expect( baseElement - .querySelector(".rc-dropdown") - .classList.contains("rc-dropdown-hidden") + .querySelector('.rc-dropdown') + .classList.contains('rc-dropdown-hidden'), ).toBeTruthy(); }); - it("re-align works", async () => { + it('re-align works', async () => { jest.useFakeTimers(); const onPopupAlign = jest.fn(); @@ -168,7 +165,7 @@ describe("dropdown", () => { ); const { container } = render( { - + , ); expect(onPopupAlign).not.toHaveBeenCalled(); - fireEvent.click(container.querySelector(".my-btn")); + fireEvent.click(container.querySelector('.my-btn')); await waitForTime(); expect(onPopupAlign).toHaveBeenCalled(); @@ -189,30 +186,30 @@ describe("dropdown", () => { jest.useRealTimers(); }); - it("Test default minOverlayWidthMatchTrigger", async () => { + it('Test default minOverlayWidthMatchTrigger', async () => { jest.useFakeTimers(); const overlayWidth = 50; const overlay =
Test
; const { container, baseElement } = render( - + - + , ); - await triggerResize(container.querySelector("button")); + await triggerResize(container.querySelector('button')); - expect(baseElement.querySelector(".rc-dropdown")).toHaveStyle({ - minWidth: "100px", + expect(baseElement.querySelector('.rc-dropdown')).toHaveStyle({ + minWidth: '100px', }); jest.useRealTimers(); }); - it("user pass minOverlayWidthMatchTrigger", async () => { + it('user pass minOverlayWidthMatchTrigger', async () => { jest.useFakeTimers(); const overlayWidth = 50; @@ -220,7 +217,7 @@ describe("dropdown", () => { const { container, baseElement } = render( { - + , ); - await triggerResize(container.querySelector("button")); + await triggerResize(container.querySelector('button')); - expect(baseElement.querySelector(".rc-dropdown")).not.toHaveStyle({ - minWidth: "100px", + expect(baseElement.querySelector('.rc-dropdown')).not.toHaveStyle({ + minWidth: '100px', }); jest.useRealTimers(); }); - it("should support default openClassName", () => { + it('should support default openClassName', () => { const overlay =
Test
; const { container } = render( - + , ); - fireEvent.click(container.querySelector(".my-button")); + fireEvent.click(container.querySelector('.my-button')); expect( container - .querySelector(".my-button") - .classList.contains("rc-dropdown-open") + .querySelector('.my-button') + .classList.contains('rc-dropdown-open'), ).toBeTruthy(); - fireEvent.click(container.querySelector(".my-button")); + fireEvent.click(container.querySelector('.my-button')); expect( container - .querySelector(".my-button") - .classList.contains("rc-dropdown-open") + .querySelector('.my-button') + .classList.contains('rc-dropdown-open'), ).toBeFalsy(); }); - it("should support custom openClassName", async () => { + it('should support custom openClassName', async () => { const overlay =
Test
; const { container } = render( { - + , ); - fireEvent.click(container.querySelector(".my-button")); + fireEvent.click(container.querySelector('.my-button')); expect( - container.querySelector(".my-button").classList.contains("opened") + container.querySelector('.my-button').classList.contains('opened'), ).toBeTruthy(); - fireEvent.click(container.querySelector(".my-button")); + fireEvent.click(container.querySelector('.my-button')); expect( - container.querySelector(".my-button").classList.contains("opened") + container.querySelector('.my-button').classList.contains('opened'), ).toBeFalsy(); }); - it("overlay callback", async () => { + it('overlay callback', async () => { const overlay =
Test
; const { container, baseElement } = render( - overlay}> + overlay}> - + , ); - fireEvent.click(container.querySelector(".my-button")); + fireEvent.click(container.querySelector('.my-button')); expect( baseElement - .querySelector(".rc-dropdown") - .classList.contains("rc-dropdown-hidden") + .querySelector('.rc-dropdown') + .classList.contains('rc-dropdown-hidden'), ).toBeFalsy(); }); - it("should support arrow", async () => { + it('should support arrow', async () => { const overlay =
Test
; const { container, baseElement } = render( - + - + , ); - fireEvent.click(container.querySelector(".my-button")); + fireEvent.click(container.querySelector('.my-button')); await sleep(500); expect( baseElement - .querySelector(".rc-dropdown") - .classList.contains("rc-dropdown-show-arrow") + .querySelector('.rc-dropdown') + .classList.contains('rc-dropdown-show-arrow'), ).toBeTruthy(); expect( baseElement - .querySelector(".rc-dropdown") - .firstElementChild.classList.contains("rc-dropdown-arrow") + .querySelector('.rc-dropdown') + .firstElementChild.classList.contains('rc-dropdown-arrow'), ).toBeTruthy(); }); - it("Keyboard navigation works", async () => { + it('Keyboard navigation works', async () => { jest.useFakeTimers(); const overlay = ( @@ -344,90 +341,90 @@ describe("dropdown", () => { ); const { container, baseElement } = render( - + - + , ); - const trigger = container.querySelector(".my-button"); + const trigger = container.querySelector('.my-button'); // Open menu; fireEvent.click(trigger); await waitForTime(); expect( baseElement - .querySelector(".rc-dropdown") - .classList.contains("rc-dropdown-hidden") + .querySelector('.rc-dropdown') + .classList.contains('rc-dropdown-hidden'), ).toBeFalsy(); // Close menu with Esc - fireEvent.keyDown(window, { key: "Esc", keyCode: 27 }); + fireEvent.keyDown(window, { key: 'Esc', keyCode: 27 }); await waitForTime(); - expect(document.activeElement.className).toContain("my-button"); + expect(document.activeElement.className).toContain('my-button'); // Open menu fireEvent.click(trigger); await waitForTime(); expect( baseElement - .querySelector(".rc-dropdown") - .classList.contains("rc-dropdown-hidden") + .querySelector('.rc-dropdown') + .classList.contains('rc-dropdown-hidden'), ).toBeFalsy(); // Focus menu with Tab - window.dispatchEvent(new KeyboardEvent("keydown", { keyCode: 9 })); // Tab - expect(document.activeElement.className).toContain("menu"); + window.dispatchEvent(new KeyboardEvent('keydown', { keyCode: 9 })); // Tab + expect(document.activeElement.className).toContain('menu'); // Close menu with Tab - window.dispatchEvent(new KeyboardEvent("keydown", { keyCode: 9 })); // Tab + window.dispatchEvent(new KeyboardEvent('keydown', { keyCode: 9 })); // Tab await waitForTime(); - expect(document.activeElement.className).toContain("my-button"); + expect(document.activeElement.className).toContain('my-button'); jest.useRealTimers(); }); - it("Tab should close menu if overlay cannot be focused", async () => { + it('Tab should close menu if overlay cannot be focused', async () => { jest.useFakeTimers(); const Overlay = () =>
test
; const { container, baseElement } = render( - }> + }> - + , ); - const trigger = container.querySelector(".my-button"); + const trigger = container.querySelector('.my-button'); // Open menu; fireEvent.click(trigger); await waitForTime(); expect( baseElement - .querySelector(".rc-dropdown") - .classList.contains("rc-dropdown-hidden") + .querySelector('.rc-dropdown') + .classList.contains('rc-dropdown-hidden'), ).toBeFalsy(); // Close menu with Esc - fireEvent.keyDown(window, { key: "Esc", keyCode: 27 }); + fireEvent.keyDown(window, { key: 'Esc', keyCode: 27 }); await waitForTime(); - expect(document.activeElement.className).toContain("my-button"); + expect(document.activeElement.className).toContain('my-button'); // Open menu fireEvent.click(trigger); await waitForTime(); expect( baseElement - .querySelector(".rc-dropdown") - .classList.contains("rc-dropdown-hidden") + .querySelector('.rc-dropdown') + .classList.contains('rc-dropdown-hidden'), ).toBeFalsy(); // Close menu with Tab - window.dispatchEvent(new KeyboardEvent("keydown", { keyCode: 9 })); // Tab + window.dispatchEvent(new KeyboardEvent('keydown', { keyCode: 9 })); // Tab await waitForTime(); - expect(document.activeElement.className).toContain("my-button"); + expect(document.activeElement.className).toContain('my-button'); jest.useRealTimers(); }); - it("keyboard should work if menu is wrapped", async () => { + it('keyboard should work if menu is wrapped', async () => { const overlay = (
@@ -439,45 +436,45 @@ describe("dropdown", () => {
); const { container, baseElement } = render( - + - + , ); - const trigger = container.querySelector(".my-button"); + const trigger = container.querySelector('.my-button'); // Open menu fireEvent.click(trigger); await sleep(200); expect( baseElement - .querySelector(".rc-dropdown") - .classList.contains("rc-dropdown-hidden") + .querySelector('.rc-dropdown') + .classList.contains('rc-dropdown-hidden'), ).toBeFalsy(); // Close menu with Esc - window.dispatchEvent(new KeyboardEvent("keydown", { keyCode: 27 })); // Esc + window.dispatchEvent(new KeyboardEvent('keydown', { keyCode: 27 })); // Esc await sleep(200); - expect(document.activeElement.className).toContain("my-button"); + expect(document.activeElement.className).toContain('my-button'); // Open menu fireEvent.click(trigger); await sleep(200); expect( baseElement - .querySelector(".rc-dropdown") - .classList.contains("rc-dropdown-hidden") + .querySelector('.rc-dropdown') + .classList.contains('rc-dropdown-hidden'), ).toBeFalsy(); // Focus menu with Tab - window.dispatchEvent(new KeyboardEvent("keydown", { keyCode: 9 })); // Tab + window.dispatchEvent(new KeyboardEvent('keydown', { keyCode: 9 })); // Tab // Close menu with Tab - window.dispatchEvent(new KeyboardEvent("keydown", { keyCode: 9 })); // Tab + window.dispatchEvent(new KeyboardEvent('keydown', { keyCode: 9 })); // Tab await sleep(200); - expect(document.activeElement.className).toContain("my-button"); + expect(document.activeElement.className).toContain('my-button'); }); - it("support Menu expandIcon", async () => { + it('support Menu expandIcon', async () => { const props = { overlay: ( }> @@ -494,13 +491,13 @@ describe("dropdown", () => { const { container } = render( - + , ); await sleep(500); - expect(container.querySelector("#customExpandIcon")).toBeTruthy(); + expect(container.querySelector('#customExpandIcon')).toBeTruthy(); }); - it("should support customized menuRef", async () => { + it('should support customized menuRef', async () => { const menuRef = createRef(); const props = { overlay: ( @@ -514,14 +511,14 @@ describe("dropdown", () => { render( - + , ); await sleep(500); expect(menuRef.current).toBeTruthy(); }); - it("should support trigger which not support focus", async () => { + it('should support trigger which not support focus', async () => { jest.useFakeTimers(); const Button = forwardRef>( (props, ref) => { @@ -537,11 +534,11 @@ describe("dropdown", () => { trigger ); - } + }, ); const { container, baseElement } = render( node} overlay={ @@ -550,16 +547,16 @@ describe("dropdown", () => { } > ); const { container } = render( - + - + , ); - const trigger = container.querySelector(".my-button"); + const trigger = container.querySelector('.my-button'); // Open menu fireEvent.click(trigger); @@ -584,18 +581,36 @@ describe("dropdown", () => { expect( container - .querySelector(".rc-dropdown") - .classList.contains("rc-dropdown-hidden") + .querySelector('.rc-dropdown') + .classList.contains('rc-dropdown-hidden'), ).toBeFalsy(); - expect(document.activeElement.className).toContain("menu"); + expect(document.activeElement.className).toContain('menu'); // Close menu with Tab - window.dispatchEvent(new KeyboardEvent("keydown", { keyCode: 9 })); // Tab + window.dispatchEvent(new KeyboardEvent('keydown', { keyCode: 9 })); // Tab await waitForTime(); - expect(document.activeElement.className).toContain("my-button"); + expect(document.activeElement.className).toContain('my-button'); jest.useRealTimers(); }); + + it('children cannot be given ref should not throw', () => { + const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + const Component = () =>
test
; + + render( + test}> + + , + ); + expect(errorSpy).not.toHaveBeenCalledWith( + expect.stringContaining( + 'Warning: Function components cannot be given refs', + ), + expect.anything(), + expect.anything(), + ); + }); }); diff --git a/tsconfig.json b/tsconfig.json index c6bd6b6..6b571e9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,7 +5,7 @@ "module": "esnext", "target": "esnext", "moduleResolution": "node", - "jsx": "react-jsx", + "jsx": "react", "skipLibCheck": true }, "include": ["./src", "./tests", "./typings/"],