From 1511f183581dcbe58e41ab97515603bfb836f4af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E7=88=B1=E5=90=83=E7=99=BD=E8=90=9D?= =?UTF-8?q?=E5=8D=9C?= Date: Wed, 26 Oct 2022 15:25:09 +0800 Subject: [PATCH] feat: Relayout of Picker (#500) * chore: init * refactor: Use presets hooks * feat: preset style * chore: rm console * test: clean up * test: update cov * chore: CI update * chore: LGTM * test: fix part test * test: update snapshot * test: rewrite part of test * test: more test case --- .fatherrc.js | 14 ++-- .github/workflows/main.yml | 2 +- assets/index.less | 14 ++++ docs/examples/basic.tsx | 8 ++- package.json | 13 ++-- src/PanelContext.tsx | 1 - src/Picker.tsx | 61 +++++++++++------ src/PickerPanel.tsx | 9 +-- src/PresetPanel.tsx | 38 +++++++++++ src/RangePicker.tsx | 87 +++++++++++++++--------- src/hooks/usePresets.ts | 32 +++++++++ src/interface.ts | 9 ++- src/utils/getRanges.tsx | 22 +----- tests/__snapshots__/picker.spec.tsx.snap | 2 +- tests/__snapshots__/range.spec.tsx.snap | 2 +- tests/components.spec.tsx | 11 ++- tests/keyboard.spec.tsx | 34 ++++----- tests/panel.spec.tsx | 80 +++++++++++----------- tests/picker.spec.tsx | 45 ++++++++---- tests/range.spec.tsx | 64 +++++++++-------- tests/setup.js | 1 - 21 files changed, 331 insertions(+), 218 deletions(-) create mode 100644 src/PresetPanel.tsx create mode 100644 src/hooks/usePresets.ts diff --git a/.fatherrc.js b/.fatherrc.js index 912aa0a..4ddbafd 100644 --- a/.fatherrc.js +++ b/.fatherrc.js @@ -1,9 +1,5 @@ -export default { - cjs: 'babel', - esm: { type: 'babel', importLibToEs: true }, - preCommit: { - eslint: true, - prettier: true, - }, - runtimeHelpers: true, -}; +import { defineConfig } from 'father'; + +export default defineConfig({ + plugins: ['@rc-component/father-plugin'], +}); \ No newline at end of file diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e4edf95..7e33cc4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -15,7 +15,7 @@ jobs: - uses: actions/setup-node@v1 with: - node-version: '12' + node-version: '16' - name: cache package-lock.json uses: actions/cache@v2 diff --git a/assets/index.less b/assets/index.less index 0178d04..b4acb9a 100644 --- a/assets/index.less +++ b/assets/index.less @@ -150,6 +150,14 @@ } } + // Preset + &-presets { + margin: 0; + padding: 0; + list-style: none; + background: #CCCCFF; + } + &-footer, &-picker-footer { background: green; @@ -451,4 +459,10 @@ vertical-align: top; transition: margin 0.3s; } + + &-panel-layout { + display: flex; + flex-wrap: nowrap; + align-items: stretch; + } } diff --git a/docs/examples/basic.tsx b/docs/examples/basic.tsx index d2d346f..f9012e1 100644 --- a/docs/examples/basic.tsx +++ b/docs/examples/basic.tsx @@ -28,6 +28,12 @@ export default () => { value, onSelect, onChange, + presets: [ + { + label: 'Hello World!', + value: moment(), + }, + ], }; const keyDown = (e, preventDefault) => { @@ -65,7 +71,7 @@ export default () => { defaultValue: moment('11:28:39', 'HH:mm:ss'), }} showToday - disabledTime={date => { + disabledTime={(date) => { if (date && date.isSame(defaultValue, 'date')) { return { disabledHours: () => [1, 3, 5, 7, 9, 11], diff --git a/package.json b/package.json index a3e6bcb..b7fde41 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "prepublishOnly": "npm run compile && np --yolo --no-publish", "lint": "eslint src/ --ext .ts,.tsx,.jsx,.js,.md", "prettier": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md}\"", - "test": "father test", + "test": "rc-test", "coverage": "father test --coverage", "now-build": "npm run build" }, @@ -51,6 +51,7 @@ "node": ">=8.x" }, "devDependencies": { + "@rc-component/father-plugin": "^1.0.0", "@testing-library/react": "^12", "@types/classnames": "^2.2.9", "@types/enzyme": "^3.10.3", @@ -70,23 +71,17 @@ "eslint-plugin-jest": "^26.8.2", "eslint-plugin-react-hooks": "^4.0.2", "eslint-plugin-unicorn": "^40.0.0", - "father": "^2.13.4", + "father": "^4.0.0", "glob": "^7.2.0", "less": "^3.10.3", "mockdate": "^3.0.2", "np": "^7.1.0", "prettier": "^2.0.5", + "rc-test": "^7.0.9", "react": "^16.0.0", "react-dom": "^16.0.0", - "react-test-renderer": "^16.0.0", "typescript": "^4.0.3" }, - "cnpm": { - "mode": "npm" - }, - "tnpm": { - "mode": "npm" - }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" diff --git a/src/PanelContext.tsx b/src/PanelContext.tsx index 32bccae..96bc423 100644 --- a/src/PanelContext.tsx +++ b/src/PanelContext.tsx @@ -10,7 +10,6 @@ export type PanelContextProps = { operationRef?: React.MutableRefObject; /** Only work with time panel */ hideHeader?: boolean; - panelRef?: React.Ref; hidePrevBtn?: boolean; hideNextBtn?: boolean; onDateMouseEnter?: (date: any) => void; diff --git a/src/Picker.tsx b/src/Picker.tsx index e551644..6be4273 100644 --- a/src/Picker.tsx +++ b/src/Picker.tsx @@ -27,13 +27,15 @@ import { formatValue, isEqual, parseValue } from './utils/dateUtil'; import getDataOrAriaProps, { toArray } from './utils/miscUtil'; import type { ContextOperationRefProps } from './PanelContext'; import PanelContext from './PanelContext'; -import type { CustomFormat, PickerMode } from './interface'; +import type { CustomFormat, PickerMode, PresetDate } from './interface'; import { getDefaultFormat, getInputSize, elementsContains } from './utils/uiUtil'; import usePickerInput from './hooks/usePickerInput'; import useTextValueMapping from './hooks/useTextValueMapping'; import useValueTexts from './hooks/useValueTexts'; import useHoverValue from './hooks/useHoverValue'; import { legacyPropsWarning } from './utils/warnUtil'; +import usePresets from './hooks/usePresets'; +import PresetPanel from './PresetPanel'; export type PickerRefConfig = { focus: () => void; @@ -56,6 +58,8 @@ export type PickerSharedProps = { inputReadOnly?: boolean; id?: string; + presets?: PresetDate[]; + // Value format?: string | CustomFormat | (string | CustomFormat)[]; @@ -151,6 +155,7 @@ function InnerPicker(props: PickerProps) { use12Hours, value, defaultValue, + presets, open, defaultOpen, defaultOpenValue, @@ -183,6 +188,8 @@ function InnerPicker(props: PickerProps) { const needConfirmButton: boolean = (picker === 'date' && !!showTime) || picker === 'time'; + const presetList = usePresets(presets); + // ============================ Warning ============================ if (process.env.NODE_ENV !== 'production') { legacyPropsWarning(props); @@ -393,26 +400,36 @@ function InnerPicker(props: PickerProps) { }; let panelNode: React.ReactNode = ( - - {...panelProps} - generateConfig={generateConfig} - className={classNames({ - [`${prefixCls}-panel-focused`]: !typing, - })} - value={selectedValue} - locale={locale} - tabIndex={-1} - onSelect={(date) => { - onSelect?.(date); - setSelectedValue(date); - }} - direction={direction} - onPanelChange={(viewDate, mode) => { - const { onPanelChange } = props; - onLeave(true); - onPanelChange?.(viewDate, mode); - }} - /> +
+ { + triggerChange(nextValue); + triggerOpen(false); + }} + /> + + {...panelProps} + generateConfig={generateConfig} + className={classNames({ + [`${prefixCls}-panel-focused`]: !typing, + })} + value={selectedValue} + locale={locale} + tabIndex={-1} + onSelect={(date) => { + onSelect?.(date); + setSelectedValue(date); + }} + direction={direction} + onPanelChange={(viewDate, mode) => { + const { onPanelChange } = props; + onLeave(true); + onPanelChange?.(viewDate, mode); + }} + /> +
); if (panelRender) { @@ -422,6 +439,7 @@ function InnerPicker(props: PickerProps) { const panel = (
{ e.preventDefault(); }} @@ -505,7 +523,6 @@ function InnerPicker(props: PickerProps) { value={{ operationRef, hideHeader: picker === 'time', - panelRef: panelDivRef, onSelect: onContextSelect, open: mergedOpen, defaultOpenValue, diff --git a/src/PickerPanel.tsx b/src/PickerPanel.tsx index 0f8271b..f4bf9f8 100644 --- a/src/PickerPanel.tsx +++ b/src/PickerPanel.tsx @@ -173,13 +173,7 @@ function PickerPanel(props: PickerPanelProps) { // ============================ State ============================= const panelContext = React.useContext(PanelContext); - const { - operationRef, - panelRef: panelDivRef, - onSelect: onContextSelect, - hideRanges, - defaultOpenValue, - } = panelContext; + const { operationRef, onSelect: onContextSelect, hideRanges, defaultOpenValue } = panelContext; const { inRange, panelPosition, rangedValue, hoverRangedValue } = React.useContext(RangeContext); const panelRef = React.useRef({}); @@ -553,7 +547,6 @@ function PickerPanel(props: PickerPanelProps) { onKeyDown={onInternalKeyDown} onBlur={onInternalBlur} onMouseDown={onMouseDown} - ref={panelDivRef} > {panelNode} {extraFooter || rangesNode || todayNode ? ( diff --git a/src/PresetPanel.tsx b/src/PresetPanel.tsx new file mode 100644 index 0000000..4c07439 --- /dev/null +++ b/src/PresetPanel.tsx @@ -0,0 +1,38 @@ +import * as React from 'react'; +import type { PresetDate } from './interface'; + +export interface PresetPanelProps { + prefixCls: string; + presets: PresetDate[]; + onClick: (value: T) => void; + onHover?: (value: T) => void; +} + +export default function PresetPanel(props: PresetPanelProps) { + const { prefixCls, presets, onClick, onHover } = props; + + if (!presets.length) { + return null; + } + + return ( +
    + {presets.map(({ label, value }, index) => ( +
  • { + onClick(value); + }} + onMouseEnter={() => { + onHover?.(value); + }} + onMouseLeave={() => { + onHover?.(null); + }} + > + {label} +
  • + ))} +
+ ); +} diff --git a/src/RangePicker.tsx b/src/RangePicker.tsx index 8c1349f..9962d93 100644 --- a/src/RangePicker.tsx +++ b/src/RangePicker.tsx @@ -3,7 +3,14 @@ import { useRef, useEffect, useState } from 'react'; import classNames from 'classnames'; import warning from 'rc-util/lib/warning'; import useMergedState from 'rc-util/lib/hooks/useMergedState'; -import type { DisabledTimes, PanelMode, PickerMode, RangeValue, EventValue } from './interface'; +import type { + DisabledTimes, + PanelMode, + PickerMode, + RangeValue, + EventValue, + PresetDate, +} from './interface'; import type { PickerBaseProps, PickerDateProps, PickerTimeProps, PickerRefConfig } from './Picker'; import type { SharedTimeProps } from './panels/TimePanel'; import PickerTrigger from './PickerTrigger'; @@ -34,6 +41,8 @@ import useRangeViewDates from './hooks/useRangeViewDates'; import type { DateRender } from './panels/DatePanel/DateBody'; import useHoverValue from './hooks/useHoverValue'; import { legacyPropsWarning } from './utils/warnUtil'; +import usePresets from './hooks/usePresets'; +import PresetPanel from './PresetPanel'; function reorderValues( values: RangeValue, @@ -87,6 +96,8 @@ export type RangePickerSharedProps = { placeholder?: [string, string]; disabled?: boolean | [boolean, boolean]; disabledTime?: (date: EventValue, type: RangeType) => DisabledTimes; + presets?: PresetDate, null>>[]; + /** @deprecated Please use `presets` instead */ ranges?: Record< string, Exclude, null> | (() => Exclude, null>) @@ -135,6 +146,7 @@ type OmitPickerProps = Omit< | 'onPickerValueChange' | 'onOk' | 'dateRender' + | 'presets' >; type RangeShowTimeObject = Omit, 'defaultValue'> & { @@ -160,7 +172,7 @@ export type RangePickerProps = | RangePickerTimeProps; // TMP type to fit for ts 3.9.2 -type OmitType = Omit, 'picker'> & +type OmitType = Omit, 'picker' | 'presets'> & Omit, 'picker'> & Omit, 'picker'>; @@ -198,6 +210,7 @@ function InnerRangePicker(props: RangePickerProps) { disabledTime, dateRender, panelRender, + presets, ranges, allowEmpty, allowClear, @@ -764,26 +777,23 @@ function InnerRangePicker(props: RangePickerProps) { } // ============================ Ranges ============================= - const rangeLabels = Object.keys(ranges || {}); - - const rangeList = rangeLabels.map((label) => { - const range = ranges![label]; - const newValues = typeof range === 'function' ? range() : range; - - return { - label, - onClick: () => { - triggerChange(newValues, null); - triggerOpen(false, mergedActivePickerIndex); - }, - onMouseEnter: () => { - setRangeHoverValue(newValues); - }, - onMouseLeave: () => { - setRangeHoverValue(null); - }, - }; - }); + const presetList = usePresets(presets, ranges); + + // const rangeList = presetList.map((preset) => { + // return { + // label: preset.label, + // onClick: () => { + // triggerChange(preset.value, null); + // triggerOpen(false, mergedActivePickerIndex); + // }, + // onMouseEnter: () => { + // setRangeHoverValue(preset.value); + // }, + // onMouseLeave: () => { + // setRangeHoverValue(null); + // }, + // }; + // }); // ============================= Panel ============================= function renderPanel( @@ -930,7 +940,7 @@ function InnerRangePicker(props: RangePickerProps) { !getValue(selectedValue, mergedActivePickerIndex) || (disabledDate && disabledDate(selectedValue[mergedActivePickerIndex])), locale, - rangeList, + // rangeList, onOk: () => { if (getValue(selectedValue, mergedActivePickerIndex)) { // triggerChangeOld(selectedValue); @@ -984,15 +994,28 @@ function InnerRangePicker(props: RangePickerProps) { } let mergedNodes: React.ReactNode = ( - <> -
{panels}
- {(extraNode || rangesNode) && ( -
- {extraNode} - {rangesNode} -
- )} - +
+ { + triggerChange(nextValue, null); + triggerOpen(false, mergedActivePickerIndex); + }} + onHover={(hoverValue) => { + setRangeHoverValue(hoverValue); + }} + /> +
+
{panels}
+ {(extraNode || rangesNode) && ( +
+ {extraNode} + {rangesNode} +
+ )} +
+
); if (panelRender) { diff --git a/src/hooks/usePresets.ts b/src/hooks/usePresets.ts new file mode 100644 index 0000000..506ed2f --- /dev/null +++ b/src/hooks/usePresets.ts @@ -0,0 +1,32 @@ +import * as React from 'react'; +import warning from 'rc-util/lib/warning'; +import type { PresetDate } from '../interface'; + +export default function usePresets( + presets?: PresetDate[], + legacyRanges?: Record T)>, +): PresetDate[] { + return React.useMemo(() => { + if (presets) { + return presets; + } + + if (legacyRanges) { + warning(false, '`ranges` is deprecated. Please use `presets` instead.'); + + const rangeLabels = Object.keys(legacyRanges); + + return rangeLabels.map((label) => { + const range = legacyRanges[label]; + const newValues = typeof range === 'function' ? (range as any)() : range; + + return { + label, + value: newValues, + }; + }); + } + + return []; + }, [presets, legacyRanges]); +} diff --git a/src/interface.ts b/src/interface.ts index ef362e2..537ca49 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -1,3 +1,4 @@ +import type React from 'react'; import type { GenerateConfig } from './generate'; export type Locale = { @@ -96,14 +97,18 @@ export type RangeValue = [EventValue, EventValue] export type Components = { button?: React.ComponentType | string; - rangeItem?: React.ComponentType | string; }; export type RangeList = { - label: string; + label: React.ReactNode; onClick: () => void; onMouseEnter: () => void; onMouseLeave: () => void; }[]; export type CustomFormat = (value: DateType) => string; + +export interface PresetDate { + label: React.ReactNode; + value: T; +} diff --git a/src/utils/getRanges.tsx b/src/utils/getRanges.tsx index 1621e62..aeef5e5 100644 --- a/src/utils/getRanges.tsx +++ b/src/utils/getRanges.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import type { Components, RangeList, Locale } from '../interface'; +import type { Components, Locale, RangeList } from '../interface'; export type RangesProps = { prefixCls: string; @@ -15,7 +15,7 @@ export type RangesProps = { export default function getRanges({ prefixCls, - rangeList = [], + // rangeList = [], components = {}, needConfirmButton, onNow, @@ -27,26 +27,10 @@ export default function getRanges({ let presetNode: React.ReactNode; let okNode: React.ReactNode; - if (rangeList.length) { - const Item = (components.rangeItem || 'span') as any; - - presetNode = ( - <> - {rangeList.map(({ label, onClick, onMouseEnter, onMouseLeave }) => ( -
  • - - {label} - -
  • - ))} - - ); - } - if (needConfirmButton) { const Button = (components.button || 'button') as any; - if (onNow && !presetNode && showNow !== false) { + if (onNow && showNow !== false) { presetNode = (
  • diff --git a/tests/__snapshots__/picker.spec.tsx.snap b/tests/__snapshots__/picker.spec.tsx.snap index ef67cdf..0ad1a3a 100644 --- a/tests/__snapshots__/picker.spec.tsx.snap +++ b/tests/__snapshots__/picker.spec.tsx.snap @@ -44,7 +44,7 @@ exports[`Picker.Basic inputRender 1`] = ` `; exports[`Picker.Basic panelRender 1`] = ` -Array [ +[
    diff --git a/tests/__snapshots__/range.spec.tsx.snap b/tests/__snapshots__/range.spec.tsx.snap index a6c21b7..3f5f428 100644 --- a/tests/__snapshots__/range.spec.tsx.snap +++ b/tests/__snapshots__/range.spec.tsx.snap @@ -91,7 +91,7 @@ exports[`Picker.Range onPanelChange is array args should render correctly in rtl `; exports[`Picker.Range panelRender 1`] = ` -Array [ +[
    diff --git a/tests/components.spec.tsx b/tests/components.spec.tsx index 32ab7ea..9f638b2 100644 --- a/tests/components.spec.tsx +++ b/tests/components.spec.tsx @@ -18,14 +18,14 @@ describe('Picker.Components', () => { }); [ - { name: 'RangePicker', component: MomentRangePicker, ranges: true }, + { name: 'RangePicker', component: MomentRangePicker }, { name: 'Picker', component: MomentPicker }, { name: 'PickerPanel', component: MomentPickerPanel }, - ].forEach(({ name, component, ranges }) => { + ].forEach(({ name, component }) => { it(name, () => { const Component = component as any; - const Button: React.FC = props =>

    ; - const Item: React.FC = props =>

    ; + const Button: React.FC = (props) =>

    ; + const Item: React.FC = (props) =>

    ; const wrapper = mount( { ); expect(wrapper.find('.rc-picker-footer').find('h1')).toHaveLength(1); - if (ranges) { - expect(wrapper.find('.rc-picker-footer').find('h2')).toHaveLength(1); - } }); }); }); diff --git a/tests/keyboard.spec.tsx b/tests/keyboard.spec.tsx index e8855d5..531b04d 100644 --- a/tests/keyboard.spec.tsx +++ b/tests/keyboard.spec.tsx @@ -1,15 +1,13 @@ -import React from 'react'; -import MockDate from 'mockdate'; -import { act } from 'react-dom/test-utils'; import KeyCode from 'rc-util/lib/KeyCode'; +import { act } from 'react-dom/test-utils'; import { - mount, getMoment, isSame, MomentPicker, MomentPickerPanel, - Wrapper, MomentRangePicker, + mount, + Wrapper, } from './util/commonUtil'; describe('Picker.Keyboard', () => { @@ -17,12 +15,13 @@ describe('Picker.Keyboard', () => { wrapper.find('.rc-picker-panel').simulate('keyDown', { which: keyCode, ...info }); } - beforeAll(() => { - MockDate.set(getMoment('1990-09-03 00:00:00').toDate()); + beforeEach(() => { + jest.useFakeTimers().setSystemTime(getMoment('1990-09-03 00:00:00').valueOf()); }); - afterAll(() => { - MockDate.reset(); + afterEach(() => { + jest.clearAllTimers(); + jest.useRealTimers(); }); it('open to select', () => { @@ -396,10 +395,7 @@ describe('Picker.Keyboard', () => { jest.runAllTimers(); }); expect( - wrapper - .find('.rc-picker-input') - .last() - .hasClass('rc-picker-input-active'), + wrapper.find('.rc-picker-input').last().hasClass('rc-picker-input-active'), ).toBeTruthy(); onCalendarChange.mockReset(); @@ -436,16 +432,10 @@ describe('Picker.Keyboard', () => { .first() .simulate('change', { target: { value: '2000-01-01' } }); wrapper.keyDown(KeyCode.ESC); - expect( - wrapper - .find('input') - .first() - .props().value, - ).toEqual(''); + expect(wrapper.find('input').first().props().value).toEqual(''); }); it('move based on current date on first keyboard event', () => { - jest.useFakeTimers(); const onCalendarChange = jest.fn(); const onChange = jest.fn(); const wrapper = mount( @@ -486,7 +476,7 @@ describe('Picker.Keyboard', () => { showTime onSelect={onSelect} onChange={onChange} - disabledDate={date => date.date() % 2 === 0} + disabledDate={(date) => date.date() % 2 === 0} />, ); wrapper.find('input').simulate('focus'); @@ -517,7 +507,7 @@ describe('Picker.Keyboard', () => { date.date() % 2 === 0} + disabledDate={(date) => date.date() % 2 === 0} />, ); diff --git a/tests/panel.spec.tsx b/tests/panel.spec.tsx index d2c3bdf..09d7fa4 100644 --- a/tests/panel.spec.tsx +++ b/tests/panel.spec.tsx @@ -1,19 +1,31 @@ -import React from 'react'; -import MockDate from 'mockdate'; import moment from 'moment'; -import { resetWarned } from 'rc-util/lib/warning'; import { spyElementPrototypes } from 'rc-util/lib/test/domHook'; -import { mount, getMoment, isSame, MomentPickerPanel } from './util/commonUtil'; -import zhCN from '../src/locale/zh_CN'; +import { resetWarned } from 'rc-util/lib/warning'; import enUS from '../src/locale/en_US'; +import zhCN from '../src/locale/zh_CN'; +import { getMoment, isSame, MomentPickerPanel, mount } from './util/commonUtil'; + +jest.mock('../src/utils/uiUtil', () => { + const origin = jest.requireActual('../src/utils/uiUtil'); + + return { + ...origin, + scrollTo: (...args) => { + global.scrollCalled = true; + return origin.scrollTo(...args); + }, + }; +}); describe('Picker.Panel', () => { - beforeAll(() => { - MockDate.set(getMoment('1990-09-03 00:00:00').toDate()); + beforeEach(() => { + global.scrollCalled = false; + jest.useFakeTimers().setSystemTime(getMoment('1990-09-03 00:00:00').valueOf()); }); afterAll(() => { - MockDate.reset(); + jest.clearAllTimers(); + jest.useRealTimers(); }); describe('value', () => { @@ -93,45 +105,31 @@ describe('Picker.Panel', () => { }); }); - describe('time click to scroll', () => { - [true, false].forEach((bool) => { - it(`spy requestAnimationFrame: ${bool}`, () => { - let scrollTop = 90; - const domSpy = spyElementPrototypes(HTMLElement, { - scrollTop: { - get: () => scrollTop, - set: ((_: Function, value: number) => { - scrollTop = value; - }) as any, - }, - }); - - let requestAnimationFrameSpy = jest.spyOn(global, 'requestAnimationFrame' as any); + it('time click to scroll', () => { + let scrollTop = 90; - // Spy to trigger 2 way of test for checking case cover - if (bool) { - requestAnimationFrameSpy = requestAnimationFrameSpy.mockImplementation( - window.setTimeout as any, - ); - } - - jest.useFakeTimers(); - const wrapper = mount(); + const domSpy = spyElementPrototypes(HTMLElement, { + scrollTop: { + get: () => scrollTop, + set: ((_: Function, value: number) => { + scrollTop = value; + }) as any, + }, + }); - // Multiple times should only one work - wrapper.find('ul').first().find('li').at(3).simulate('click'); + jest.useFakeTimers(); + const wrapper = mount(); - wrapper.find('ul').first().find('li').at(11).simulate('click'); - jest.runAllTimers(); + // Multiple times should only one work + wrapper.find('ul').first().find('li').at(3).simulate('click'); - expect(requestAnimationFrameSpy).toHaveBeenCalled(); + wrapper.find('ul').first().find('li').at(11).simulate('click'); + jest.runAllTimers(); + expect(global.scrollCalled).toBeTruthy(); - jest.useRealTimers(); + jest.useRealTimers(); - domSpy.mockRestore(); - requestAnimationFrameSpy.mockRestore(); - }); - }); + domSpy.mockRestore(); }); describe('click button to switch', () => { diff --git a/tests/picker.spec.tsx b/tests/picker.spec.tsx index 8f96766..a931625 100644 --- a/tests/picker.spec.tsx +++ b/tests/picker.spec.tsx @@ -1,22 +1,24 @@ -import React from 'react'; -import MockDate from 'mockdate'; -import { act } from 'react-dom/test-utils'; -import { spyElementPrototypes } from 'rc-util/lib/test/domHook'; +import { fireEvent, render } from '@testing-library/react'; +import type { Moment } from 'moment'; +import moment from 'moment'; import KeyCode from 'rc-util/lib/KeyCode'; +import { spyElementPrototypes } from 'rc-util/lib/test/domHook'; import { resetWarned } from 'rc-util/lib/warning'; -import moment from 'moment'; -import type { Moment } from 'moment'; +import React from 'react'; +import { act } from 'react-dom/test-utils'; import type { PanelMode, PickerMode } from '../src/interface'; -import { mount, getMoment, isSame, MomentPicker } from './util/commonUtil'; -import { fireEvent, render } from '@testing-library/react'; +import { getMoment, isSame, MomentPicker, mount } from './util/commonUtil'; + +const fakeTime = getMoment('1990-09-03 00:00:00').valueOf(); describe('Picker.Basic', () => { - beforeAll(() => { - MockDate.set(getMoment('1990-09-03 00:00:00').toDate()); + beforeEach(() => { + jest.useFakeTimers().setSystemTime(fakeTime); }); afterAll(() => { - MockDate.reset(); + jest.clearAllTimers(); + jest.useRealTimers(); }); describe('mode', () => { @@ -544,13 +546,13 @@ describe('Picker.Basic', () => { describe('time step', () => { it('work with now', () => { - MockDate.set(getMoment('1990-09-03 00:09:00').toDate()); + jest.setSystemTime(getMoment('1990-09-03 00:09:00').valueOf()) const onSelect = jest.fn(); const wrapper = mount(); wrapper.openPicker(); wrapper.find('.rc-picker-now > a').simulate('click'); expect(isSame(onSelect.mock.calls[0][0], '1990-09-03 00:00:59', 'second')).toBeTruthy(); - MockDate.set(getMoment('1990-09-03 00:00:00').toDate()); + jest.setSystemTime(getMoment('1990-09-03 00:00:00').valueOf()) }); it('should show warning when hour step is invalid', () => { const spy = jest.spyOn(console, 'error').mockImplementation(() => {}); @@ -980,4 +982,21 @@ describe('Picker.Basic', () => { wrapper.find('input').simulate('keyDown', { which: KeyCode.ENTER }); }); + + it('presets', () => { + const onChange = jest.fn(); + + const wrapper = mount( + , + ); + + expect(wrapper.find('.rc-picker-presets li').text()).toBe('Bamboo'); + wrapper.find('.rc-picker-presets li').simulate('click'); + + expect(onChange.mock.calls[0][0].format('YYYY-MM-DD')).toEqual('2000-09-03'); + }); }); diff --git a/tests/range.spec.tsx b/tests/range.spec.tsx index 2b69841..6f5a2d8 100644 --- a/tests/range.spec.tsx +++ b/tests/range.spec.tsx @@ -1,15 +1,14 @@ -import React from 'react'; -import MockDate from 'mockdate'; -import { act } from 'react-dom/test-utils'; -import KeyCode from 'rc-util/lib/KeyCode'; -import { spyElementPrototypes } from 'rc-util/lib/test/domHook'; +import { fireEvent, render } from '@testing-library/react'; import type { Moment } from 'moment'; import moment from 'moment'; -import type { Wrapper } from './util/commonUtil'; -import { mount, getMoment, isSame, MomentRangePicker } from './util/commonUtil'; -import zhCN from '../src/locale/zh_CN'; +import KeyCode from 'rc-util/lib/KeyCode'; +import { spyElementPrototypes } from 'rc-util/lib/test/domHook'; +import React from 'react'; +import { act } from 'react-dom/test-utils'; import type { PickerMode } from '../src/interface'; -import { fireEvent, render } from '@testing-library/react'; +import zhCN from '../src/locale/zh_CN'; +import type { Wrapper } from './util/commonUtil'; +import { getMoment, isSame, MomentRangePicker, mount } from './util/commonUtil'; describe('Picker.Range', () => { function matchValues(wrapper: Wrapper, value1: string, value2: string) { @@ -17,12 +16,14 @@ describe('Picker.Range', () => { expect(wrapper.find('input').last().props().value).toEqual(value2); } - beforeAll(() => { - MockDate.set(getMoment('1990-09-03 00:00:00').toDate()); + beforeEach(() => { + global.scrollCalled = false; + jest.useFakeTimers().setSystemTime(getMoment('1990-09-03 00:00:00').valueOf()); }); - afterAll(() => { - MockDate.reset(); + afterEach(() => { + jest.clearAllTimers(); + jest.useRealTimers(); }); describe('value', () => { @@ -295,7 +296,7 @@ describe('Picker.Range', () => { // Basic wrapper.openPicker(); - testNode = wrapper.find('.rc-picker-ranges li span').first(); + testNode = wrapper.find('.rc-picker-presets li').first(); expect(testNode.text()).toEqual('test'); testNode.simulate('click'); expect(onChange).toHaveBeenCalledWith( @@ -306,7 +307,7 @@ describe('Picker.Range', () => { // Function wrapper.openPicker(); - testNode = wrapper.find('.rc-picker-ranges li span').last(); + testNode = wrapper.find('.rc-picker-presets li').last(); expect(testNode.text()).toEqual('func'); testNode.simulate('click'); expect(onChange).toHaveBeenCalledWith( @@ -326,12 +327,12 @@ describe('Picker.Range', () => { ); wrapper.openPicker(); - wrapper.find('.rc-picker-preset > *').simulate('mouseEnter'); + wrapper.find('.rc-picker-presets li').simulate('mouseEnter'); expect(wrapper.findCell(11).hasClass('rc-picker-cell-range-start')).toBeTruthy(); expect(wrapper.findCell(12).hasClass('rc-picker-cell-in-range')).toBeTruthy(); expect(wrapper.findCell(13).hasClass('rc-picker-cell-range-end')).toBeTruthy(); - wrapper.find('.rc-picker-preset > *').simulate('mouseLeave'); + wrapper.find('.rc-picker-presets li').simulate('mouseLeave'); expect(wrapper.findCell(11).hasClass('rc-picker-cell-range-start')).toBeFalsy(); expect(wrapper.findCell(12).hasClass('rc-picker-cell-in-range')).toBeFalsy(); expect(wrapper.findCell(13).hasClass('rc-picker-cell-range-end')).toBeFalsy(); @@ -1201,20 +1202,21 @@ describe('Picker.Range', () => { describe('click at non-input elements', () => { it('should focus on the first element by default', () => { jest.useFakeTimers(); - const wrapper = mount(); - wrapper.find('.rc-picker').simulate('click'); - expect(wrapper.isOpen()).toBeTruthy(); + const { container } = render(); + fireEvent.click(container.querySelector('.rc-picker')); + expect(document.querySelector('.rc-picker-dropdown')).toBeTruthy(); jest.runAllTimers(); - expect(document.activeElement).toStrictEqual(wrapper.find('input').first().getDOMNode()); + expect(document.activeElement).toBe(container.querySelector('input')); jest.useRealTimers(); }); + it('should focus on the second element if first is disabled', () => { jest.useFakeTimers(); - const wrapper = mount(); - wrapper.find('.rc-picker').simulate('click'); - expect(wrapper.isOpen()).toBeTruthy(); + const { container } = render(); + fireEvent.click(container.querySelector('.rc-picker')); + expect(document.querySelector('.rc-picker-dropdown')).toBeTruthy(); jest.runAllTimers(); - expect(document.activeElement).toStrictEqual(wrapper.find('input').last().getDOMNode()); + expect(document.activeElement).toBe(container.querySelectorAll('input')[1]); jest.useRealTimers(); }); it("shouldn't let mousedown blur the input", () => { @@ -1585,7 +1587,9 @@ describe('Picker.Range', () => { />, ); wrapper.openPicker(1); - expect(wrapper.find('.rc-picker-panel-container').getDOMNode().style.marginLeft).toBe('0px'); + expect( + wrapper.find('.rc-picker-panel-container').getDOMNode().style.marginLeft, + ).toBe('0px'); mock.mockRestore(); }); @@ -1621,7 +1625,9 @@ describe('Picker.Range', () => { />, ); wrapper.openPicker(1); - expect(wrapper.find('.rc-picker-panel-container').getDOMNode().style.marginLeft).toBe('0px'); + expect( + wrapper.find('.rc-picker-panel-container').getDOMNode().style.marginLeft, + ).toBe('0px'); mock.mockRestore(); }); @@ -1657,7 +1663,9 @@ describe('Picker.Range', () => { />, ); wrapper.openPicker(1); - expect(wrapper.find('.rc-picker-panel-container').getDOMNode().style.marginLeft).toBe('295px'); + expect( + wrapper.find('.rc-picker-panel-container').getDOMNode().style.marginLeft, + ).toBe('295px'); mock.mockRestore(); }); }); diff --git a/tests/setup.js b/tests/setup.js index c0d938d..df5d140 100644 --- a/tests/setup.js +++ b/tests/setup.js @@ -41,7 +41,6 @@ Object.assign(Enzyme.ReactWrapper.prototype, { } }); if (!matchCell) { - console.log(table.html()); throw new Error('Cell not match in picker panel.'); }