mirror of
https://github.com/zhigang1992/react.git
synced 2026-01-30 17:18:35 +08:00
Merge pull request #107 from unix/test
test(auto-complete): add testcase
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`AutoComplete should render correctly 1`] = `
|
||||
<AutoComplete
|
||||
className=""
|
||||
clearable={false}
|
||||
disabled={false}
|
||||
initialValue=""
|
||||
options={Array []}
|
||||
size="medium"
|
||||
/>
|
||||
`;
|
||||
File diff suppressed because it is too large
Load Diff
54
components/auto-complete/__tests__/index.test.tsx
Normal file
54
components/auto-complete/__tests__/index.test.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import React from 'react'
|
||||
import { mount } from 'enzyme'
|
||||
import { AutoComplete } from '../../index'
|
||||
import { nativeEvent } from '../../../tests/utils'
|
||||
|
||||
describe('AutoComplete', () => {
|
||||
it('should render correctly', () => {
|
||||
const wrapper = mount(<AutoComplete />)
|
||||
expect(() => wrapper.unmount()).not.toThrow()
|
||||
expect(<AutoComplete />).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('should support sizes and status', () => {
|
||||
const wrapper = mount(
|
||||
<div>
|
||||
<AutoComplete status="secondary" />
|
||||
<AutoComplete status="success" />
|
||||
<AutoComplete size="mini" />
|
||||
<AutoComplete size="large" />
|
||||
</div>
|
||||
)
|
||||
expect(() => wrapper.unmount()).not.toThrow()
|
||||
})
|
||||
|
||||
it('should set input value from initial value', () => {
|
||||
let wrapper = mount(<AutoComplete initialValue="value" />)
|
||||
let input = wrapper.find('input').at(0).getDOMNode()
|
||||
expect((input as HTMLInputElement).value).toEqual('value')
|
||||
|
||||
wrapper = mount(<AutoComplete value="value2" />)
|
||||
input = wrapper.find('input').at(0).getDOMNode()
|
||||
expect((input as HTMLInputElement).value).toEqual('value2')
|
||||
})
|
||||
|
||||
it('should render clear icon', () => {
|
||||
const wrapper = mount(<AutoComplete initialValue="value" />)
|
||||
expect(wrapper.find('svg').length).toBe(0)
|
||||
|
||||
wrapper.setProps({ clearable: true })
|
||||
expect(wrapper.find('svg').length).toBe(1)
|
||||
|
||||
wrapper.find('svg').simulate('click', nativeEvent)
|
||||
const input = wrapper.find('input').at(0).getDOMNode()
|
||||
expect((input as HTMLInputElement).value).toEqual('')
|
||||
})
|
||||
|
||||
it('should reponse width change', () => {
|
||||
const wrapper = mount(<AutoComplete initialValue="value" width="100px" />)
|
||||
expect(wrapper.prop('width')).toEqual('100px')
|
||||
|
||||
wrapper.setProps({ width: '200px' })
|
||||
expect(wrapper.prop('width')).toEqual('200px')
|
||||
})
|
||||
})
|
||||
142
components/auto-complete/__tests__/search.test.tsx
Normal file
142
components/auto-complete/__tests__/search.test.tsx
Normal file
@@ -0,0 +1,142 @@
|
||||
import React from 'react'
|
||||
import { mount, render } from 'enzyme'
|
||||
import { AutoComplete } from '../../index'
|
||||
import { nativeEvent } from '../../../tests/utils'
|
||||
const mockOptions = [
|
||||
{ label: 'London', value: 'london' },
|
||||
]
|
||||
|
||||
describe('AutoComplete Search', () => {
|
||||
it('should render options element', () => {
|
||||
const wrapper = mount(<AutoComplete options={mockOptions} />)
|
||||
wrapper.find('input').at(0).simulate('focus')
|
||||
let dropdown = wrapper.find('.auto-complete-dropdown').children()
|
||||
expect(dropdown.length).not.toBe(0)
|
||||
|
||||
wrapper.find('input').at(0).simulate('blur')
|
||||
expect(() => wrapper.unmount()).not.toThrow()
|
||||
})
|
||||
|
||||
it('should update value when dropdown clicked', () => {
|
||||
let value = ''
|
||||
const wrapper = mount(<AutoComplete options={mockOptions} onChange={val => value = val} />)
|
||||
wrapper.find('input').at(0).simulate('focus')
|
||||
wrapper.find('.item').at(0).simulate('click', nativeEvent)
|
||||
expect(value).toEqual('london')
|
||||
})
|
||||
|
||||
it('should ignore events when disabled', () => {
|
||||
let value = ''
|
||||
const wrapper = mount(
|
||||
<AutoComplete disabled options={mockOptions}
|
||||
onChange={val => value = val} />
|
||||
)
|
||||
wrapper.find('input').at(0).simulate('focus')
|
||||
wrapper.find('.item').at(0).simulate('click', nativeEvent)
|
||||
expect(value).not.toEqual('london')
|
||||
})
|
||||
|
||||
it('should render searching component', () => {
|
||||
let wrapper = mount(
|
||||
<AutoComplete searching={false} options={mockOptions}/>
|
||||
)
|
||||
wrapper.setProps({ searching: true })
|
||||
wrapper.find('input').at(0).simulate('focus')
|
||||
let dropdown = wrapper.find('.auto-complete-dropdown')
|
||||
expect(dropdown.text()).not.toContain('london')
|
||||
|
||||
const loading = wrapper.find('.loading')
|
||||
expect(loading.length).not.toBe(0)
|
||||
|
||||
wrapper = mount(
|
||||
<AutoComplete searching options={mockOptions}>
|
||||
<AutoComplete.Searching>
|
||||
<span>waiting...</span>
|
||||
</AutoComplete.Searching>
|
||||
</AutoComplete>
|
||||
)
|
||||
wrapper.find('input').at(0).simulate('focus')
|
||||
dropdown = wrapper.find('.auto-complete-dropdown')
|
||||
expect(dropdown.text()).toContain('waiting')
|
||||
})
|
||||
|
||||
it('should hide empty component', () => {
|
||||
let wrapper = render(
|
||||
<AutoComplete placeholder="Enter here">
|
||||
<AutoComplete.Empty hidden />
|
||||
</AutoComplete>
|
||||
)
|
||||
expect(wrapper).toMatchSnapshot()
|
||||
|
||||
wrapper = render(
|
||||
<AutoComplete placeholder="Enter here">
|
||||
<AutoComplete.Empty>empty</AutoComplete.Empty>
|
||||
</AutoComplete>
|
||||
)
|
||||
expect(wrapper).toMatchSnapshot()
|
||||
|
||||
const mountWrapper = mount(
|
||||
<AutoComplete placeholder="Enter here" initialValue="value">
|
||||
<AutoComplete.Empty>empty</AutoComplete.Empty>
|
||||
</AutoComplete>
|
||||
)
|
||||
mountWrapper.find('input').at(0).simulate('focus')
|
||||
const text = mountWrapper.find('.auto-complete-dropdown').text()
|
||||
expect(text).toContain('empty')
|
||||
|
||||
const mountWrapper2 = mount(
|
||||
<AutoComplete placeholder="Enter here" initialValue="value">
|
||||
<AutoComplete.Empty hidden>empty</AutoComplete.Empty>
|
||||
</AutoComplete>
|
||||
)
|
||||
mountWrapper2.find('input').at(0).simulate('focus')
|
||||
const text2 = mountWrapper2.find('.auto-complete-dropdown').text()
|
||||
expect(text2).not.toContain('empty')
|
||||
})
|
||||
|
||||
it('should trigger search handler', () => {
|
||||
const handler = jest.fn()
|
||||
const wrapper = mount(<AutoComplete initialValue="value" onSearch={handler} />)
|
||||
const input = wrapper.find('input').at(0)
|
||||
input.simulate('focus')
|
||||
input.simulate('change')
|
||||
;(input.getDOMNode() as HTMLInputElement).value = 'value'
|
||||
expect(handler).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should trigger select and change handler', () => {
|
||||
const selectHandler = jest.fn()
|
||||
const changeHandler = jest.fn()
|
||||
const wrapper = mount(
|
||||
<AutoComplete options={mockOptions} initialValue="value"
|
||||
onSelect={selectHandler} onChange={changeHandler} />
|
||||
)
|
||||
wrapper.find('input').at(0).simulate('focus')
|
||||
wrapper.find('.item').at(0).simulate('click', nativeEvent)
|
||||
expect(selectHandler).toHaveBeenCalled()
|
||||
expect(changeHandler).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should work with custom options', () => {
|
||||
const changeHandler = jest.fn()
|
||||
const makeOption = (label: string, value: string): any => (
|
||||
<AutoComplete.Option value={value}>
|
||||
<span>{label}</span>
|
||||
</AutoComplete.Option>
|
||||
)
|
||||
const options = mockOptions
|
||||
.map(({ label, value }) => makeOption(label, value) as typeof AutoComplete.Option)
|
||||
const wrapper = mount(
|
||||
<AutoComplete options={options} onChange={changeHandler} />
|
||||
)
|
||||
wrapper.find('input').at(0).simulate('focus')
|
||||
wrapper.find('.item').at(0).simulate('click', nativeEvent)
|
||||
expect(changeHandler).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should work correctly without options', () => {
|
||||
const wrapper = mount(<AutoComplete options={[]} />)
|
||||
expect(() => wrapper.unmount()).not.toThrow()
|
||||
})
|
||||
|
||||
})
|
||||
@@ -6,7 +6,7 @@ export interface AutoCompleteConfig {
|
||||
updateValue?: Function
|
||||
visible?: boolean
|
||||
updateVisible?: Function
|
||||
size?: NormalSizes
|
||||
size: NormalSizes
|
||||
ref?: MutableRefObject<HTMLElement | null>
|
||||
}
|
||||
|
||||
|
||||
@@ -20,10 +20,10 @@ const AutoCompleteDropdown: React.FC<React.PropsWithChildren<Props>> = ({
|
||||
|
||||
return (
|
||||
<Dropdown parent={ref} visible={visible}>
|
||||
<div className="auto-dropdown-dropdown" onClick={clickHandler}>
|
||||
<div className="auto-complete-dropdown" onClick={clickHandler}>
|
||||
{children}
|
||||
<style jsx>{`
|
||||
.auto-dropdown-dropdown {
|
||||
.auto-complete-dropdown {
|
||||
border-radius: ${theme.layout.radius};
|
||||
box-shadow: ${theme.expressiveness.shadowLarge};
|
||||
background-color: ${theme.palette.background};
|
||||
|
||||
@@ -6,27 +6,25 @@ import { NormalSizes } from 'components/utils/prop-types'
|
||||
|
||||
interface Props {
|
||||
value: string
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
disabled: false,
|
||||
}
|
||||
|
||||
export type AutoCompleteItemProps = Props & typeof defaultProps & React.HTMLAttributes<any>
|
||||
|
||||
const getSizes = (size?: NormalSizes) => {
|
||||
const getSizes = (size: NormalSizes) => {
|
||||
const fontSizes: { [key in NormalSizes]: string } = {
|
||||
mini: '.7rem',
|
||||
small: '.75rem',
|
||||
medium: '.875rem',
|
||||
large: '1rem',
|
||||
}
|
||||
return size ? fontSizes[size] : fontSizes.medium
|
||||
return fontSizes[size]
|
||||
}
|
||||
|
||||
const AutoCompleteItem: React.FC<React.PropsWithChildren<AutoCompleteItemProps>> = ({
|
||||
value: identValue, children, disabled,
|
||||
value: identValue, children,
|
||||
}) => {
|
||||
const theme = useTheme()
|
||||
const { value, updateValue, size } = useAutoCompleteContext()
|
||||
@@ -54,7 +52,7 @@ const AutoCompleteItem: React.FC<React.PropsWithChildren<AutoCompleteItemProps>>
|
||||
color: ${theme.palette.foreground};
|
||||
user-select: none;
|
||||
border: 0;
|
||||
cursor: ${disabled ? 'not-allowed' : 'pointer'};
|
||||
cursor: pointer;
|
||||
transition: background 0.2s ease 0s, border-color 0.2s ease 0s;
|
||||
}
|
||||
|
||||
|
||||
@@ -43,10 +43,8 @@ const defaultProps = {
|
||||
type NativeAttrs = Omit<React.InputHTMLAttributes<any>, keyof Props>
|
||||
export type AutoCompleteProps = Props & typeof defaultProps & NativeAttrs
|
||||
|
||||
const childrenToOptionsNode = (options: Array<AutoCompleteOption>) => {
|
||||
if (options.length === 0) return null
|
||||
|
||||
return options.map((item, index) => {
|
||||
const childrenToOptionsNode = (options: Array<AutoCompleteOption>) =>
|
||||
options.map((item, index) => {
|
||||
const key = `auto-complete-item-${index}`
|
||||
if (React.isValidElement(item)) return React.cloneElement(item, { key })
|
||||
const validItem = item as AutoCompleteOption
|
||||
@@ -57,7 +55,6 @@ const childrenToOptionsNode = (options: Array<AutoCompleteOption>) => {
|
||||
</AutoCompleteItem>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
// When the search is not set, the "clearable" icon can be displayed in the original location.
|
||||
// When the search is seted, at least one element should exist to avoid re-render.
|
||||
@@ -68,7 +65,8 @@ const getSearchIcon = (searching?: boolean) => {
|
||||
|
||||
const AutoComplete: React.FC<React.PropsWithChildren<AutoCompleteProps>> = ({
|
||||
options, initialValue: customInitialValue, onSelect, onSearch, onChange,
|
||||
searching, children, size, status, value, width, clearable, ...props
|
||||
searching, children, size, status, value, width, clearable,
|
||||
disabled, ...props
|
||||
}) => {
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
const [state, setState] = useState<string>(customInitialValue)
|
||||
@@ -93,6 +91,7 @@ const AutoComplete: React.FC<React.PropsWithChildren<AutoCompleteProps>> = ({
|
||||
)
|
||||
|
||||
const updateValue = (val: string) => {
|
||||
if (disabled) return
|
||||
onSelect && onSelect(val)
|
||||
setState(val)
|
||||
}
|
||||
@@ -102,7 +101,9 @@ const AutoComplete: React.FC<React.PropsWithChildren<AutoCompleteProps>> = ({
|
||||
setState(event.target.value)
|
||||
}
|
||||
|
||||
useEffect(() => onChange && onChange(state), [state])
|
||||
useEffect(() => {
|
||||
onChange && onChange(state)
|
||||
}, [state])
|
||||
useEffect(() => {
|
||||
if (value === undefined) return
|
||||
setState(value)
|
||||
@@ -126,6 +127,7 @@ const AutoComplete: React.FC<React.PropsWithChildren<AutoCompleteProps>> = ({
|
||||
const inputProps = {
|
||||
...props,
|
||||
width,
|
||||
disabled,
|
||||
value: state,
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user