Repository: zeit-ui/react
Pull request #123 (2020-04-23 04:50:05)

test: add test cases
## PR Checklist

- [x] Fix linting errors
- [x] Label has been added


## Change information





Patch:
diff --git a/components/radio/radio-group.tsx b/components/radio/radio-group.tsx
index 835eb9a..1278a52 100644
--- a/components/radio/radio-group.tsx
+++ b/components/radio/radio-group.tsx
@@ -4,7 +4,7 @@ import useTheme from '../styles/use-theme'
 import { RadioContext } from './radio-context'
 
 interface Props {
-  value: string
+  value?: string
   initialValue?: string
   disabled?: boolean
   onChange?: (value: string) => void
@@ -42,6 +42,7 @@ const RadioGroup: React.FC<React.PropsWithChildren<RadioGroupProps>> = React.mem
   },[disabled, selfVal])
   
   useEffect(() => {
+    if (value === undefined) return
     setSelfVal(value)
   }, [value])
 
diff --git a/components/radio/radio.tsx b/components/radio/radio.tsx
index 49b895b..88dff18 100644
--- a/components/radio/radio.tsx
+++ b/components/radio/radio.tsx
@@ -20,10 +20,9 @@ export interface RadioEvent {
 interface Props {
   checked?: boolean
   value?: string
-  id?: string
   className?: string
   disabled?: boolean
-  onChange: (e: RadioEvent) => void
+  onChange?: (e: RadioEvent) => void
 }
 
 const defaultProps = {
@@ -35,8 +34,8 @@ type NativeAttrs = Omit<React.InputHTMLAttributes<any>, keyof Props>
 export type RadioProps = Props & typeof defaultProps & NativeAttrs
 
 const Radio: React.FC<React.PropsWithChildren<RadioProps>> = React.memo(({
-  className, id: customId, checked, onChange, disabled,
-  value: radioValue, children, ...props
+  className, checked, onChange, disabled, value: radioValue,
+  children, ...props
 }) => {
   const theme = useTheme()
   const [selfChecked, setSelfChecked] = useState<boolean>(!!checked)
@@ -50,10 +49,11 @@ const Radio: React.FC<React.PropsWithChildren<RadioProps>> = React.memo(({
     if (radioValue === undefined) {
       useWarning('Props "value" must be deinfed if in the Radio.Group.', 'Radio')
     }
-    useEffect(() => setSelfChecked(groupValue === radioValue), [groupValue, radioValue])
+    useEffect(() => {
+      setSelfChecked(groupValue === radioValue)
+    }, [groupValue, radioValue])
   }
   
-  // const id = useMemo(() => customId || `zeit-ui-radio-${label}`, [customId, label])
   const isDisabled = useMemo(() => disabled || disabledAll, [disabled, disabledAll])
   const changeHandler = (event: React.ChangeEvent) => {
     if (isDisabled) return
@@ -72,12 +72,15 @@ const Radio: React.FC<React.PropsWithChildren<RadioProps>> = React.memo(({
     onChange && onChange(selfEvent)
   }
   
-  useEffect(() => setSelfChecked(!!checked), [checked])
+  useEffect(() => {
+    if (checked === undefined) return
+    setSelfChecked(!!checked)
+  }, [checked])
 
   return (
     <div className={`radio ${className}`}>
-      <label htmlFor={customId}>
-        <input type="radio" value={radioValue} id={customId}
+      <label>
+        <input type="radio" value={radioValue}
           checked={selfChecked}
           onChange={changeHandler}
           {...props} />
diff --git a/components/select/select-option.tsx b/components/select/select-option.tsx
index 5948d32..46bcc4d 100644
--- a/components/select/select-option.tsx
+++ b/components/select/select-option.tsx
@@ -53,10 +53,10 @@ const SelectOption: React.FC<React.PropsWithChildren<SelectOptionProps>> = ({
 
   return (
     <>
-      <div className={className} onClick={clickHandler} {...props}>{children}</div>
+      <div className={`option ${className}`} onClick={clickHandler} {...props}>{children}</div>
   
       <style jsx>{`
-        div {
+        .option {
           display: flex;
           justify-content: flex-start;
           align-items: center;
@@ -73,17 +73,17 @@ const SelectOption: React.FC<React.PropsWithChildren<SelectOptionProps>> = ({
           transition: background 0.2s ease 0s, border-color 0.2s ease 0s;
         }
         
-        div:first-of-type {
+        .option:first-of-type {
           border-top-left-radius: ${theme.layout.radius};
           border-top-right-radius: ${theme.layout.radius};
         }
         
-        div:last-of-type {
+        .option:last-of-type {
           border-bottom-left-radius: ${theme.layout.radius};
           border-bottom-right-radius: ${theme.layout.radius};
         }
         
-        div:hover {
+        .option:hover {
           background-color: ${theme.palette.accents_1};
         }
       `}</style>


Test patch:
diff --git a/components/radio/__tests__/__snapshots__/group.test.tsx.snap b/components/radio/__tests__/__snapshots__/group.test.tsx.snap
new file mode 100644
index 0000000..6bdf6e8
--- /dev/null
+++ b/components/radio/__tests__/__snapshots__/group.test.tsx.snap
@@ -0,0 +1,265 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Radio Group should render correctly 1`] = `
+"<div><div class=\\"radio-group \\"><div class=\\"radio \\"><label><input type=\\"radio\\" value=\\"1\\"><span class=\\"name\\">1</span><span class=\\"point\\"></span></label><style>
+        input {
+          opacity: 0;
+          visibility: hidden;
+          overflow: hidden;
+          width: 1px;
+          height: 1px;
+          top: -1000px;
+          right: -1000px;
+          position: fixed;
+        }
+        
+        .radio {
+          display: flex;
+          width: initial;
+          align-items: flex-start;
+          line-height: 1.5rem;
+          position: relative;
+        }
+        
+        label {
+          display: flex;
+          flex-direction: column;
+          justify-content: flex-start;
+          margin-left: 1.375rem;
+          color: #000;
+          cursor: pointer;
+        }
+        
+        .name {
+          font-size: 1rem;
+          font-weight: bold;
+          user-select: none;
+        }
+        
+        .point {
+          position: absolute;
+          left: 0;
+          top: 6px;
+          height: .875rem;
+          width: .875rem;
+          border-radius: 50%;
+          border: 1px solid #eaeaea;
+          transition: all 0.2s ease 0s;
+        }
+        
+        .point:before {
+          content: '';
+          position: absolute;
+          left: -1px;
+          top: -1px;
+          height: .875rem;
+          width: .875rem;
+          border-radius: 50%;
+          transform: scale(1);
+          transition: all 0.2s ease;
+          background-color: #000;
+        }
+      </style></div><div class=\\"radio \\"><label><input type=\\"radio\\" value=\\"2\\"><span class=\\"name\\">2</span><span class=\\"point\\"></span></label><style>
+        input {
+          opacity: 0;
+          visibility: hidden;
+          overflow: hidden;
+          width: 1px;
+          height: 1px;
+          top: -1000px;
+          right: -1000px;
+          position: fixed;
+        }
+        
+        .radio {
+          display: flex;
+          width: initial;
+          align-items: flex-start;
+          line-height: 1.5rem;
+          position: relative;
+        }
+        
+        label {
+          display: flex;
+          flex-direction: column;
+          justify-content: flex-start;
+          margin-left: 1.375rem;
+          color: #000;
+          cursor: pointer;
+        }
+        
+        .name {
+          font-size: 1rem;
+          font-weight: bold;
+          user-select: none;
+        }
+        
+        .point {
+          position: absolute;
+          left: 0;
+          top: 6px;
+          height: .875rem;
+          width: .875rem;
+          border-radius: 50%;
+          border: 1px solid #eaeaea;
+          transition: all 0.2s ease 0s;
+        }
+        
+        .point:before {
+          content: '';
+          position: absolute;
+          left: -1px;
+          top: -1px;
+          height: .875rem;
+          width: .875rem;
+          border-radius: 50%;
+          transform: scale(0);
+          transition: all 0.2s ease;
+          background-color: #000;
+        }
+      </style></div></div><style>
+        .radio-group {
+          display: flex;
+          flex-direction: column;
+        }
+        
+        .radio-group :global(.radio) {
+          margin-top: 16pt;
+          margin-left: 0;
+        }
+        
+        .radio-group :global(.radio:first-of-type) {
+          margin: 0;
+        }
+      </style><div class=\\"radio-group \\"><div class=\\"radio \\"><label><input type=\\"radio\\" value=\\"1\\"><span class=\\"name\\">1</span><span class=\\"point\\"></span></label><style>
+        input {
+          opacity: 0;
+          visibility: hidden;
+          overflow: hidden;
+          width: 1px;
+          height: 1px;
+          top: -1000px;
+          right: -1000px;
+          position: fixed;
+        }
+        
+        .radio {
+          display: flex;
+          width: initial;
+          align-items: flex-start;
+          line-height: 1.5rem;
+          position: relative;
+        }
+        
+        label {
+          display: flex;
+          flex-direction: column;
+          justify-content: flex-start;
+          margin-left: 1.375rem;
+          color: #000;
+          cursor: pointer;
+        }
+        
+        .name {
+          font-size: 1rem;
+          font-weight: bold;
+          user-select: none;
+        }
+        
+        .point {
+          position: absolute;
+          left: 0;
+          top: 6px;
+          height: .875rem;
+          width: .875rem;
+          border-radius: 50%;
+          border: 1px solid #eaeaea;
+          transition: all 0.2s ease 0s;
+        }
+        
+        .point:before {
+          content: '';
+          position: absolute;
+          left: -1px;
+          top: -1px;
+          height: .875rem;
+          width: .875rem;
+          border-radius: 50%;
+          transform: scale(1);
+          transition: all 0.2s ease;
+          background-color: #000;
+        }
+      </style></div><div class=\\"radio \\"><label><input type=\\"radio\\" value=\\"2\\"><span class=\\"name\\">2</span><span class=\\"point\\"></span></label><style>
+        input {
+          opacity: 0;
+          visibility: hidden;
+          overflow: hidden;
+          width: 1px;
+          height: 1px;
+          top: -1000px;
+          right: -1000px;
+          position: fixed;
+        }
+        
+        .radio {
+          display: flex;
+          width: initial;
+          align-items: flex-start;
+          line-height: 1.5rem;
+          position: relative;
+        }
+        
+        label {
+          display: flex;
+          flex-direction: column;
+          justify-content: flex-start;
+          margin-left: 1.375rem;
+          color: #000;
+          cursor: pointer;
+        }
+        
+        .name {
+          font-size: 1rem;
+          font-weight: bold;
+          user-select: none;
+        }
+        
+        .point {
+          position: absolute;
+          left: 0;
+          top: 6px;
+          height: .875rem;
+          width: .875rem;
+          border-radius: 50%;
+          border: 1px solid #eaeaea;
+          transition: all 0.2s ease 0s;
+        }
+        
+        .point:before {
+          content: '';
+          position: absolute;
+          left: -1px;
+          top: -1px;
+          height: .875rem;
+          width: .875rem;
+          border-radius: 50%;
+          transform: scale(0);
+          transition: all 0.2s ease;
+          background-color: #000;
+        }
+      </style></div></div><style>
+        .radio-group {
+          display: flex;
+          flex-direction: col;
+        }
+        
+        .radio-group :global(.radio) {
+          margin-top: 0;
+          margin-left: 16pt;
+        }
+        
+        .radio-group :global(.radio:first-of-type) {
+          margin: 0;
+        }
+      </style></div>"
+`;
diff --git a/components/radio/__tests__/__snapshots__/index.test.tsx.snap b/components/radio/__tests__/__snapshots__/index.test.tsx.snap
new file mode 100644
index 0000000..2bc1d91
--- /dev/null
+++ b/components/radio/__tests__/__snapshots__/index.test.tsx.snap
@@ -0,0 +1,251 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Radio should render correctly 1`] = `
+"<div class=\\"radio \\"><label><input type=\\"radio\\" value=\\"\\"><span class=\\"name\\">Option</span><span class=\\"point\\"></span></label><style>
+        input {
+          opacity: 0;
+          visibility: hidden;
+          overflow: hidden;
+          width: 1px;
+          height: 1px;
+          top: -1000px;
+          right: -1000px;
+          position: fixed;
+        }
+        
+        .radio {
+          display: flex;
+          width: initial;
+          align-items: flex-start;
+          line-height: 1.5rem;
+          position: relative;
+        }
+        
+        label {
+          display: flex;
+          flex-direction: column;
+          justify-content: flex-start;
+          margin-left: 1.375rem;
+          color: #000;
+          cursor: pointer;
+        }
+        
+        .name {
+          font-size: 1rem;
+          font-weight: bold;
+          user-select: none;
+        }
+        
+        .point {
+          position: absolute;
+          left: 0;
+          top: 6px;
+          height: .875rem;
+          width: .875rem;
+          border-radius: 50%;
+          border: 1px solid #eaeaea;
+          transition: all 0.2s ease 0s;
+        }
+        
+        .point:before {
+          content: '';
+          position: absolute;
+          left: -1px;
+          top: -1px;
+          height: .875rem;
+          width: .875rem;
+          border-radius: 50%;
+          transform: scale(0);
+          transition: all 0.2s ease;
+          background-color: #000;
+        }
+      </style></div>"
+`;
+
+exports[`Radio should support react-node in description 1`] = `
+"<div><div class=\\"radio \\"><label><input type=\\"radio\\" value=\\"\\"><span class=\\"name\\">Option</span><span class=\\"point\\"></span></label><style>
+        input {
+          opacity: 0;
+          visibility: hidden;
+          overflow: hidden;
+          width: 1px;
+          height: 1px;
+          top: -1000px;
+          right: -1000px;
+          position: fixed;
+        }
+        
+        .radio {
+          display: flex;
+          width: initial;
+          align-items: flex-start;
+          line-height: 1.5rem;
+          position: relative;
+        }
+        
+        label {
+          display: flex;
+          flex-direction: column;
+          justify-content: flex-start;
+          margin-left: 1.375rem;
+          color: #000;
+          cursor: pointer;
+        }
+        
+        .name {
+          font-size: 1rem;
+          font-weight: bold;
+          user-select: none;
+        }
+        
+        .point {
+          position: absolute;
+          left: 0;
+          top: 6px;
+          height: .875rem;
+          width: .875rem;
+          border-radius: 50%;
+          border: 1px solid #eaeaea;
+          transition: all 0.2s ease 0s;
+        }
+        
+        .point:before {
+          content: '';
+          position: absolute;
+          left: -1px;
+          top: -1px;
+          height: .875rem;
+          width: .875rem;
+          border-radius: 50%;
+          transform: scale(0);
+          transition: all 0.2s ease;
+          background-color: #000;
+        }
+      </style></div><div class=\\"radio \\"><label><input type=\\"radio\\" value=\\"\\"><span class=\\"name\\">Option 1</span><span class=\\"\\">Description for Option1<style>
+        span {
+          color: #999;
+          font-size: .875rem;
+        }
+      </style></span><span class=\\"point\\"></span></label><style>
+        input {
+          opacity: 0;
+          visibility: hidden;
+          overflow: hidden;
+          width: 1px;
+          height: 1px;
+          top: -1000px;
+          right: -1000px;
+          position: fixed;
+        }
+        
+        .radio {
+          display: flex;
+          width: initial;
+          align-items: flex-start;
+          line-height: 1.5rem;
+          position: relative;
+        }
+        
+        label {
+          display: flex;
+          flex-direction: column;
+          justify-content: flex-start;
+          margin-left: 1.375rem;
+          color: #000;
+          cursor: pointer;
+        }
+        
+        .name {
+          font-size: 1rem;
+          font-weight: bold;
+          user-select: none;
+        }
+        
+        .point {
+          position: absolute;
+          left: 0;
+          top: 6px;
+          height: .875rem;
+          width: .875rem;
+          border-radius: 50%;
+          border: 1px solid #eaeaea;
+          transition: all 0.2s ease 0s;
+        }
+        
+        .point:before {
+          content: '';
+          position: absolute;
+          left: -1px;
+          top: -1px;
+          height: .875rem;
+          width: .875rem;
+          border-radius: 50%;
+          transform: scale(0);
+          transition: all 0.2s ease;
+          background-color: #000;
+        }
+      </style></div><div class=\\"radio \\"><label><input type=\\"radio\\" value=\\"\\"><span class=\\"name\\">Option 1</span><span class=\\"\\"><b>Description</b> for Option1<style>
+        span {
+          color: #999;
+          font-size: .875rem;
+        }
+      </style></span><span class=\\"point\\"></span></label><style>
+        input {
+          opacity: 0;
+          visibility: hidden;
+          overflow: hidden;
+          width: 1px;
+          height: 1px;
+          top: -1000px;
+          right: -1000px;
+          position: fixed;
+        }
+        
+        .radio {
+          display: flex;
+          width: initial;
+          align-items: flex-start;
+          line-height: 1.5rem;
+          position: relative;
+        }
+        
+        label {
+          display: flex;
+          flex-direction: column;
+          justify-content: flex-start;
+          margin-left: 1.375rem;
+          color: #000;
+          cursor: pointer;
+        }
+        
+        .name {
+          font-size: 1rem;
+          font-weight: bold;
+          user-select: none;
+        }
+        
+        .point {
+          position: absolute;
+          left: 0;
+          top: 6px;
+          height: .875rem;
+          width: .875rem;
+          border-radius: 50%;
+          border: 1px solid #eaeaea;
+          transition: all 0.2s ease 0s;
+        }
+        
+        .point:before {
+          content: '';
+          position: absolute;
+          left: -1px;
+          top: -1px;
+          height: .875rem;
+          width: .875rem;
+          border-radius: 50%;
+          transform: scale(0);
+          transition: all 0.2s ease;
+          background-color: #000;
+        }
+      </style></div></div>"
+`;
diff --git a/components/radio/__tests__/group.test.tsx b/components/radio/__tests__/group.test.tsx
new file mode 100644
index 0000000..b4045e5
--- /dev/null
+++ b/components/radio/__tests__/group.test.tsx
@@ -0,0 +1,120 @@
+import React from 'react'
+import { mount } from 'enzyme'
+import { Radio } from 'components'
+import { nativeEvent } from 'tests/utils'
+
+describe('Radio Group', () => {
+  it('should render correctly', () => {
+    const wrapper = mount(
+      <div>
+        <Radio.Group value="1">
+          <Radio value="1">1</Radio>
+          <Radio value="2">2</Radio>
+        </Radio.Group>
+        <Radio.Group value="1" useRow>
+          <Radio value="1">1</Radio>
+          <Radio value="2">2</Radio>
+        </Radio.Group>
+      </div>
+    )
+    expect(wrapper.html()).toMatchSnapshot()
+    expect(() => wrapper.unmount()).not.toThrow()
+  })
+  
+  it('should trigger events in group', () => {
+    let value = ''
+    const changeHandler = jest.fn()
+      .mockImplementation(val => value = val)
+    const wrapper = mount(
+      <Radio.Group onChange={changeHandler}>
+        <Radio value="1">Option 1</Radio>
+        <Radio value="2">Option 2</Radio>
+      </Radio.Group>
+    )
+
+    wrapper.find('input').at(0)
+      .simulate('change', {
+        ...nativeEvent,
+        target: { checked: true },
+      })
+    expect(changeHandler).toHaveBeenCalled()
+    expect(value).toEqual('1')
+    changeHandler.mockRestore()
+  })
+
+  it('should ignore events when disabled', () => {
+    const changeHandler = jest.fn()
+    const wrapper = mount(
+      <Radio.Group onChange={changeHandler} disabled>
+        <Radio value="1">Option 1</Radio>
+        <Radio value="2">Option 2</Radio>
+      </Radio.Group>
+    )
+
+    wrapper.find('input').at(0)
+      .simulate('change', {
+        ...nativeEvent,
+        target: { checked: true },
+      })
+    expect(changeHandler).not.toHaveBeenCalled()
+    changeHandler.mockRestore()
+  })
+
+  it('should render correctly with inital-value', () => {
+    const wrapper = mount(
+      <Radio.Group initialValue="2">
+        <Radio value="1">Option 1</Radio>
+        <Radio value="2">Option 2</Radio>
+      </Radio.Group>
+    )
+    const input = wrapper.find('input').at(1).getDOMNode() as HTMLInputElement
+    expect(input.checked).toEqual(true)
+  })
+  
+  it('should be warning when value unset', () => {
+    const errorSpy = jest.spyOn(console, 'error')
+      .mockImplementation(() => {})
+    mount(
+      <Radio.Group>
+        <Radio>Option 1</Radio>
+        <Radio value="2">Option 2</Radio>
+      </Radio.Group>
+    )
+    
+    expect(errorSpy).toHaveBeenCalled()
+    errorSpy.mockRestore()
+  })
+  
+  it('should be warning when checked is set', () => {
+    const errorSpy = jest.spyOn(console, 'error')
+      .mockImplementation(() => {})
+    mount(
+      <Radio.Group>
+        <Radio value="1" checked>Option 1</Radio>
+        <Radio value="2">Option 2</Radio>
+      </Radio.Group>
+    )
+    
+    expect(errorSpy).toHaveBeenCalled()
+    errorSpy.mockRestore()
+  })
+  
+  it('should set state through value prop', () => {
+    const wrapper = mount(
+      <Radio.Group>
+        <Radio value="1">Option 1</Radio>
+        <Radio value="2">Option 2</Radio>
+      </Radio.Group>
+    )
+    let input = wrapper.find('input').at(0).getDOMNode() as HTMLInputElement
+    expect(input.checked).toEqual(false)
+    
+    wrapper.setProps({ value: '1' })
+    input = wrapper.find('input').at(0).getDOMNode() as HTMLInputElement
+    expect(input.checked).toEqual(true)
+  
+    wrapper.setProps({ value: '2' })
+    input = wrapper.find('input').at(1).getDOMNode() as HTMLInputElement
+    expect(input.checked).toEqual(true)
+  })
+})
diff --git a/components/radio/__tests__/index.test.tsx b/components/radio/__tests__/index.test.tsx
new file mode 100644
index 0000000..2520252
--- /dev/null
+++ b/components/radio/__tests__/index.test.tsx
@@ -0,0 +1,65 @@
+import React from 'react'
+import { mount } from 'enzyme'
+import { Radio } from 'components'
+import { nativeEvent } from 'tests/utils'
+
+describe('Radio', () => {
+  it('should render correctly', () => {
+    const wrapper = mount(<Radio checked={false}>Option</Radio>)
+    expect(wrapper.html()).toMatchSnapshot()
+    expect(() => wrapper.unmount()).not.toThrow()
+  })
+  
+  it('should render correctly with checked prop', () => {
+    const wrapper = mount(<Radio>Option</Radio>)
+    wrapper.setProps({ checked: false })
+    let input = wrapper.find('input').at(0).getDOMNode() as HTMLInputElement
+    expect(input.checked).toEqual(false)
+  
+    wrapper.setProps({ checked: true })
+    input = wrapper.find('input').at(0).getDOMNode() as HTMLInputElement
+    expect(input.checked).toEqual(true)
+  })
+  
+  it('should trigger events when use alone', () => {
+    const changeHandler = jest.fn()
+    const wrapper = mount(<Radio onChange={changeHandler}>Option</Radio>)
+    wrapper.find('input').at(0)
+      .simulate('change', {
+        ...nativeEvent,
+        target: { checked: true },
+      })
+    expect(changeHandler).toHaveBeenCalled()
+    changeHandler.mockRestore()
+  })
+  
+  it('should ignore events when disabled', () => {
+    const changeHandler = jest.fn()
+    const wrapper = mount(<Radio onChange={changeHandler} disabled>Option</Radio>)
+    wrapper.find('input').at(0)
+      .simulate('change', {
+        ...nativeEvent,
+        target: { checked: true },
+      })
+    expect(changeHandler).not.toHaveBeenCalled()
+    changeHandler.mockRestore()
+  })
+  
+  it('should support react-node in description', () => {
+    const wrapper = mount(
+      <div>
+        <Radio>Option</Radio>
+        <Radio>
+          Option 1
+          <Radio.Desc>Description for Option1</Radio.Desc>
+        </Radio>
+        <Radio>
+          Option 1
+          <Radio.Desc><b>Description</b> for Option1</Radio.Desc>
+        </Radio>
+      </div>
+    )
+    expect(wrapper.html()).toMatchSnapshot()
+    expect(() => wrapper.unmount()).not.toThrow()
+  })
+})
diff --git a/components/row/__tests__/__snapshots__/index.test.tsx.snap b/components/row/__tests__/__snapshots__/index.test.tsx.snap
new file mode 100644
index 0000000..182fae2
--- /dev/null
+++ b/components/row/__tests__/__snapshots__/index.test.tsx.snap
@@ -0,0 +1,281 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Row should render correctly 1`] = `
+"<div class=\\"row \\">row<style>
+      .row {
+        display: flex;
+        position: relative;
+        box-sizing: border-box;
+        margin-left: calc(0 * 16pt / 2);
+        margin-right: calc(0 * 16pt / 2);
+        --row-gap: calc(0 * 16pt);
+        justify-content: normal;
+        align-items: normal;
+      }
+    </style></div>"
+`;
+
+exports[`Row should work with nested 1`] = `
+"<div class=\\"row \\"><div class=\\"row \\"><div class=\\"row \\"><div class=\\"col \\"><div class=\\"row \\">row<style>
+      .row {
+        display: flex;
+        position: relative;
+        box-sizing: border-box;
+        margin-left: calc(0 * 16pt / 2);
+        margin-right: calc(0 * 16pt / 2);
+        --row-gap: calc(0 * 16pt);
+        justify-content: normal;
+        align-items: normal;
+      }
+    </style></div><style>
+        .col {
+          float: left;
+          box-sizing: border-box;
+          padding-left: calc(var(--row-gap) / 2);
+          padding-right: calc(var(--row-gap) / 2);
+          width: 100%;
+          margin-left: 0%;
+        }
+      </style></div><style>
+      .row {
+        display: flex;
+        position: relative;
+        box-sizing: border-box;
+        margin-left: calc(0 * 16pt / 2);
+        margin-right: calc(0 * 16pt / 2);
+        --row-gap: calc(0 * 16pt);
+        justify-content: normal;
+        align-items: normal;
+      }
+    </style></div><style>
+      .row {
+        display: flex;
+        position: relative;
+        box-sizing: border-box;
+        margin-left: calc(0 * 16pt / 2);
+        margin-right: calc(0 * 16pt / 2);
+        --row-gap: calc(0 * 16pt);
+        justify-content: normal;
+        align-items: normal;
+      }
+    </style></div><style>
+      .row {
+        display: flex;
+        position: relative;
+        box-sizing: border-box;
+        margin-left: calc(0 * 16pt / 2);
+        margin-right: calc(0 * 16pt / 2);
+        --row-gap: calc(0 * 16pt);
+        justify-content: normal;
+        align-items: normal;
+      }
+    </style></div>"
+`;
+
+exports[`Row the children should be aligned correctly 1`] = `
+"<div><div class=\\"row \\"><div class=\\"col \\"><style>
+        .col {
+          float: left;
+          box-sizing: border-box;
+          padding-left: calc(var(--row-gap) / 2);
+          padding-right: calc(var(--row-gap) / 2);
+          width: 100%;
+          margin-left: 0%;
+        }
+      </style></div><style>
+      .row {
+        display: flex;
+        position: relative;
+        box-sizing: border-box;
+        margin-left: calc(0 * 16pt / 2);
+        margin-right: calc(0 * 16pt / 2);
+        --row-gap: calc(0 * 16pt);
+        justify-content: flex-end;
+        align-items: normal;
+      }
+    </style></div><div class=\\"row \\"><div class=\\"col \\"><style>
+        .col {
+          float: left;
+          box-sizing: border-box;
+          padding-left: calc(var(--row-gap) / 2);
+          padding-right: calc(var(--row-gap) / 2);
+          width: 100%;
+          margin-left: 0%;
+        }
+      </style></div><style>
+      .row {
+        display: flex;
+        position: relative;
+        box-sizing: border-box;
+        margin-left: calc(0 * 16pt / 2);
+        margin-right: calc(0 * 16pt / 2);
+        --row-gap: calc(0 * 16pt);
+        justify-content: center;
+        align-items: normal;
+      }
+    </style></div><div class=\\"row \\"><div class=\\"col \\"><style>
+        .col {
+          float: left;
+          box-sizing: border-box;
+          padding-left: calc(var(--row-gap) / 2);
+          padding-right: calc(var(--row-gap) / 2);
+          width: 100%;
+          margin-left: 0%;
+        }
+      </style></div><style>
+      .row {
+        display: flex;
+        position: relative;
+        box-sizing: border-box;
+        margin-left: calc(0 * 16pt / 2);
+        margin-right: calc(0 * 16pt / 2);
+        --row-gap: calc(0 * 16pt);
+        justify-content: space-around;
+        align-items: normal;
+      }
+    </style></div><div class=\\"row \\"><div class=\\"col \\"><style>
+        .col {
+          float: left;
+          box-sizing: border-box;
+          padding-left: calc(var(--row-gap) / 2);
+          padding-right: calc(var(--row-gap) / 2);
+          width: 100%;
+          margin-left: 0%;
+        }
+      </style></div><style>
+      .row {
+        display: flex;
+        position: relative;
+        box-sizing: border-box;
+        margin-left: calc(0 * 16pt / 2);
+        margin-right: calc(0 * 16pt / 2);
+        --row-gap: calc(0 * 16pt);
+        justify-content: space-between;
+        align-items: normal;
+      }
+    </style></div><div class=\\"row \\"><div class=\\"col \\"><style>
+        .col {
+          float: left;
+          box-sizing: border-box;
+          padding-left: calc(var(--row-gap) / 2);
+          padding-right: calc(var(--row-gap) / 2);
+          width: 100%;
+          margin-left: 0%;
+        }
+      </style></div><style>
+      .row {
+        display: flex;
+        position: relative;
+        box-sizing: border-box;
+        margin-left: calc(0 * 16pt / 2);
+        margin-right: calc(0 * 16pt / 2);
+        --row-gap: calc(0 * 16pt);
+        justify-content: normal;
+        align-items: normal;
+      }
+    </style></div><div class=\\"row \\"><div class=\\"col \\"><style>
+        .col {
+          float: left;
+          box-sizing: border-box;
+          padding-left: calc(var(--row-gap) / 2);
+          padding-right: calc(var(--row-gap) / 2);
+          width: 100%;
+          margin-left: 0%;
+        }
+      </style></div><style>
+      .row {
+        display: flex;
+        position: relative;
+        box-sizing: border-box;
+        margin-left: calc(0 * 16pt / 2);
+        margin-right: calc(0 * 16pt / 2);
+        --row-gap: calc(0 * 16pt);
+        justify-content: normal;
+        align-items: center;
+      }
+    </style></div><div class=\\"row \\"><div class=\\"col \\"><style>
+        .col {
+          float: left;
+          box-sizing: border-box;
+          padding-left: calc(var(--row-gap) / 2);
+          padding-right: calc(var(--row-gap) / 2);
+          width: 100%;
+          margin-left: 0%;
+        }
+      </style></div><style>
+      .row {
+        display: flex;
+        position: relative;
+        box-sizing: border-box;
+        margin-left: calc(0 * 16pt / 2);
+        margin-right: calc(0 * 16pt / 2);
+        --row-gap: calc(0 * 16pt);
+        justify-content: normal;
+        align-items: flex-end;
+      }
+    </style></div></div>"
+`;
+
+exports[`Row the children should have the correct spacing 1`] = `
+"<div><div class=\\"row \\"><div class=\\"col \\"><style>
+        .col {
+          float: left;
+          box-sizing: border-box;
+          padding-left: calc(var(--row-gap) / 2);
+          padding-right: calc(var(--row-gap) / 2);
+          width: 100%;
+          margin-left: 0%;
+        }
+      </style></div><style>
+      .row {
+        display: flex;
+        position: relative;
+        box-sizing: border-box;
+        margin-left: calc(1 * 16pt / 2);
+        margin-right: calc(1 * 16pt / 2);
+        --row-gap: calc(1 * 16pt);
+        justify-content: normal;
+        align-items: normal;
+      }
+    </style></div><div class=\\"row \\"><div class=\\"col \\"><style>
+        .col {
+          float: left;
+          box-sizing: border-box;
+          padding-left: calc(var(--row-gap) / 2);
+          padding-right: calc(var(--row-gap) / 2);
+          width: 100%;
+          margin-left: 0%;
+        }
+      </style></div><style>
+      .row {
+        display: flex;
+        position: relative;
+        box-sizing: border-box;
+        margin-left: calc(2 * 16pt / 2);
+        margin-right: calc(2 * 16pt / 2);
+        --row-gap: calc(2 * 16pt);
+        justify-content: normal;
+        align-items: normal;
+      }
+    </style></div><div class=\\"row \\"><div class=\\"col \\"><style>
+        .col {
+          float: left;
+          box-sizing: border-box;
+          padding-left: calc(var(--row-gap) / 2);
+          padding-right: calc(var(--row-gap) / 2);
+          width: 100%;
+          margin-left: 0%;
+        }
+      </style></div><style>
+      .row {
+        display: flex;
+        position: relative;
+        box-sizing: border-box;
+        margin-left: calc(10 * 16pt / 2);
+        margin-right: calc(10 * 16pt / 2);
+        --row-gap: calc(10 * 16pt);
+        justify-content: normal;
+        align-items: normal;
+      }
+    </style></div></div>"
+`;
diff --git a/components/row/__tests__/index.test.tsx b/components/row/__tests__/index.test.tsx
new file mode 100644
index 0000000..6dd9e88
--- /dev/null
+++ b/components/row/__tests__/index.test.tsx
@@ -0,0 +1,67 @@
+import React from 'react'
+import { mount } from 'enzyme'
+import { Row, Col } from 'components'
+
+describe('Row', () => {
+  it('should render correctly', () => {
+    const wrapper = mount(<Row>row</Row>)
+    expect(wrapper.html()).toMatchSnapshot()
+    expect(() => wrapper.unmount()).not.toThrow()
+  })
+  
+  it('should be render differnet components', () => {
+    const wrapper = mount(<Row>row</Row>)
+    wrapper.setProps({ component: 'span' })
+    expect(wrapper.find('span').length).not.toBe(0)
+  
+    wrapper.setProps({ component: 'p' })
+    expect(wrapper.find('p').length).not.toBe(0)
+  
+    wrapper.setProps({ component: 'details' })
+    expect(wrapper.find('details').length).not.toBe(0)
+  })
+  
+  it('the children should be aligned correctly', () => {
+    const wrapper = mount(
+      <div>
+        <Row justify="end"><Col /></Row>
+        <Row justify="center"><Col /></Row>
+        <Row justify="space-around"><Col /></Row>
+        <Row justify="space-between"><Col /></Row>
+        <Row align="top"><Col /></Row>
+        <Row align="middle"><Col /></Row>
+        <Row align="bottom"><Col /></Row>
+      </div>
+    )
+    expect(wrapper.html()).toMatchSnapshot()
+    expect(() => wrapper.unmount()).not.toThrow()
+  })
+  
+  it('the children should have the correct spacing', () => {
+    const wrapper = mount(
+      <div>
+        <Row gap={1}><Col /></Row>
+        <Row gap={2}><Col /></Row>
+        <Row gap={10}><Col /></Row>
+      </div>
+    )
+    expect(wrapper.html()).toMatchSnapshot()
+    expect(() => wrapper.unmount()).not.toThrow()
+  })
+  
+  it('should work with nested', () => {
+    const wrapper = mount(
+      <Row>
+        <Row>
+          <Row>
+            <Col>
+              <Row>row</Row>
+            </Col>
+          </Row>
+        </Row>
+      </Row>
+    )
+    expect(wrapper.html()).toMatchSnapshot()
+    expect(() => wrapper.unmount()).not.toThrow()
+  })
+})
diff --git a/components/select/__tests__/__snapshots__/index.test.tsx.snap b/components/select/__tests__/__snapshots__/index.test.tsx.snap
new file mode 100644
index 0000000..789359f
--- /dev/null
+++ b/components/select/__tests__/__snapshots__/index.test.tsx.snap
@@ -0,0 +1,1014 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Select should render correctly 1`] = `
+"<div><div class=\\"select \\"><span class=\\"value placeholder\\"></span><div class=\\"icon\\"><svg viewBox=\\"0 0 24 24\\" width=\\"1.25em\\" height=\\"1.25em\\" stroke-width=\\"1\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\" fill=\\"none\\" shape-rendering=\\"geometricPrecision\\"><path d=\\"M6 9l6 6 6-6\\"></path><style>
+        svg {
+          color: inherit;
+          stroke: currentColor;
+          transition: all 200ms ease;
+        }
+      </style></svg></div><style>
+          .select {
+            display: inline-flex;
+            align-items: center;
+            user-select: none;
+            white-space: nowrap;
+            position: relative;
+            cursor: pointer;
+            max-width: 80vw;
+            width: initial;
+            overflow: hidden;
+            transition: border 0.2s ease 0s, color 0.2s ease-out 0s, box-shadow 0.2s ease 0s;
+            border: 1px solid #eaeaea;
+            border-radius: 5px;
+            padding: 0 4pt 0 8pt;
+            height: calc(1.688 * 16pt);
+            min-width: 10rem;
+            background-color: #fff;
+          }
+          
+          .select:hover {
+            border-color: #000;
+          }
+          
+          .select:hover .icon {
+            color: #000
+          }
+          
+          .value {
+            display: inline-flex;
+            flex: 1;
+            height: 100%;
+            align-items: center;
+            line-height: 1;
+            padding: 0;
+            margin-right: 1.25rem;
+            font-size: .875rem;
+            color: #000;
+            width: calc(100% - 1.25rem);
+          }
+          
+          .value > :global(div), .value > :global(div:hover) {
+            border-radius: 0;
+            background-color: transparent;
+            padding: 0;
+            margin: 0;
+            color: inherit;
+          }
+          
+          .placeholder {
+            color: #999;
+          }
+          
+          .icon {
+            position: absolute;
+            right: 4pt;
+            font-size: .875rem;
+            top: 50%;
+            bottom: 0;
+            transform: translateY(-50%) rotate(0deg);
+            pointer-events: none;
+            transition: transform 200ms ease;
+            display: flex;
+            align-items: center;
+            color: #666;
+          }
+        </style></div><div class=\\"select \\"><span class=\\"value placeholder\\"></span><div class=\\"icon\\"><svg viewBox=\\"0 0 24 24\\" width=\\"1.25em\\" height=\\"1.25em\\" stroke-width=\\"1\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\" fill=\\"none\\" shape-rendering=\\"geometricPrecision\\"><path d=\\"M6 9l6 6 6-6\\"></path><style>
+        svg {
+          color: inherit;
+          stroke: currentColor;
+          transition: all 200ms ease;
+        }
+      </style></svg></div><style>
+          .select {
+            display: inline-flex;
+            align-items: center;
+            user-select: none;
+            white-space: nowrap;
+            position: relative;
+            cursor: pointer;
+            max-width: 80vw;
+            width: initial;
+            overflow: hidden;
+            transition: border 0.2s ease 0s, color 0.2s ease-out 0s, box-shadow 0.2s ease 0s;
+            border: 1px solid #eaeaea;
+            border-radius: 5px;
+            padding: 0 4pt 0 8pt;
+            height: calc(1 * 16pt);
+            min-width: 6.5rem;
+            background-color: #fff;
+          }
+          
+          .select:hover {
+            border-color: #000;
+          }
+          
+          .select:hover .icon {
+            color: #000
+          }
+          
+          .value {
+            display: inline-flex;
+            flex: 1;
+            height: 100%;
+            align-items: center;
+            line-height: 1;
+            padding: 0;
+            margin-right: 1.25rem;
+            font-size: .75rem;
+            color: #000;
+            width: calc(100% - 1.25rem);
+          }
+          
+          .value > :global(div), .value > :global(div:hover) {
+            border-radius: 0;
+            background-color: transparent;
+            padding: 0;
+            margin: 0;
+            color: inherit;
+          }
+          
+          .placeholder {
+            color: #999;
+          }
+          
+          .icon {
+            position: absolute;
+            right: 4pt;
+            font-size: .75rem;
+            top: 50%;
+            bottom: 0;
+            transform: translateY(-50%) rotate(0deg);
+            pointer-events: none;
+            transition: transform 200ms ease;
+            display: flex;
+            align-items: center;
+            color: #666;
+          }
+        </style></div><div class=\\"select \\"><span class=\\"value placeholder\\"></span><div class=\\"icon\\"><svg viewBox=\\"0 0 24 24\\" width=\\"1.25em\\" height=\\"1.25em\\" stroke-width=\\"1\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\" fill=\\"none\\" shape-rendering=\\"geometricPrecision\\"><path d=\\"M6 9l6 6 6-6\\"></path><style>
+        svg {
+          color: inherit;
+          stroke: currentColor;
+          transition: all 200ms ease;
+        }
+      </style></svg></div><style>
+          .select {
+            display: inline-flex;
+            align-items: center;
+            user-select: none;
+            white-space: nowrap;
+            position: relative;
+            cursor: pointer;
+            max-width: 80vw;
+            width: initial;
+            overflow: hidden;
+            transition: border 0.2s ease 0s, color 0.2s ease-out 0s, box-shadow 0.2s ease 0s;
+            border: 1px solid #eaeaea;
+            border-radius: 5px;
+            padding: 0 4pt 0 8pt;
+            height: calc(1.344 * 16pt);
+            min-width: 8rem;
+            background-color: #fff;
+          }
+          
+          .select:hover {
+            border-color: #000;
+          }
+          
+          .select:hover .icon {
+            color: #000
+          }
+          
+          .value {
+            display: inline-flex;
+            flex: 1;
+            height: 100%;
+            align-items: center;
+            line-height: 1;
+            padding: 0;
+            margin-right: 1.25rem;
+            font-size: .75rem;
+            color: #000;
+            width: calc(100% - 1.25rem);
+          }
+          
+          .value > :global(div), .value > :global(div:hover) {
+            border-radius: 0;
+            background-color: transparent;
+            padding: 0;
+            margin: 0;
+            color: inherit;
+          }
+          
+          .placeholder {
+            color: #999;
+          }
+          
+          .icon {
+            position: absolute;
+            right: 4pt;
+            font-size: .75rem;
+            top: 50%;
+            bottom: 0;
+            transform: translateY(-50%) rotate(0deg);
+            pointer-events: none;
+            transition: transform 200ms ease;
+            display: flex;
+            align-items: center;
+            color: #666;
+          }
+        </style></div><div class=\\"select \\"><span class=\\"value placeholder\\"></span><div class=\\"icon\\"><svg viewBox=\\"0 0 24 24\\" width=\\"1.25em\\" height=\\"1.25em\\" stroke-width=\\"1\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\" fill=\\"none\\" shape-rendering=\\"geometricPrecision\\"><path d=\\"M6 9l6 6 6-6\\"></path><style>
+        svg {
+          color: inherit;
+          stroke: currentColor;
+          transition: all 200ms ease;
+        }
+      </style></svg></div><style>
+          .select {
+            display: inline-flex;
+            align-items: center;
+            user-select: none;
+            white-space: nowrap;
+            position: relative;
+            cursor: pointer;
+            max-width: 80vw;
+            width: initial;
+            overflow: hidden;
+            transition: border 0.2s ease 0s, color 0.2s ease-out 0s, box-shadow 0.2s ease 0s;
+            border: 1px solid #eaeaea;
+            border-radius: 5px;
+            padding: 0 4pt 0 8pt;
+            height: calc(2 * 16pt);
+            min-width: 12.5rem;
+            background-color: #fff;
+          }
+          
+          .select:hover {
+            border-color: #000;
+          }
+          
+          .select:hover .icon {
+            color: #000
+          }
+          
+          .value {
+            display: inline-flex;
+            flex: 1;
+            height: 100%;
+            align-items: center;
+            line-height: 1;
+            padding: 0;
+            margin-right: 1.25rem;
+            font-size: 1.225rem;
+            color: #000;
+            width: calc(100% - 1.25rem);
+          }
+          
+          .value > :global(div), .value > :global(div:hover) {
+            border-radius: 0;
+            background-color: transparent;
+            padding: 0;
+            margin: 0;
+            color: inherit;
+          }
+          
+          .placeholder {
+            color: #999;
+          }
+          
+          .icon {
+            position: absolute;
+            right: 4pt;
+            font-size: 1.225rem;
+            top: 50%;
+            bottom: 0;
+            transform: translateY(-50%) rotate(0deg);
+            pointer-events: none;
+            transition: transform 200ms ease;
+            display: flex;
+            align-items: center;
+            color: #666;
+          }
+        </style></div></div>"
+`;
+
+exports[`Select should work with different icons 1`] = `
+initialize {
+  "0": Object {
+    "attribs": Object {
+      "class": "select ",
+    },
+    "children": Array [
+      Object {
+        "attribs": Object {
+          "class": "value placeholder",
+        },
+        "children": Array [],
+        "name": "span",
+        "namespace": "http://www.w3.org/1999/xhtml",
+        "next": Object {
+          "attribs": Object {},
+          "children": Array [
+            Object {
+              "data": "
+          .select {
+            display: inline-flex;
+            align-items: center;
+            user-select: none;
+            white-space: nowrap;
+            position: relative;
+            cursor: pointer;
+            max-width: 80vw;
+            width: initial;
+            overflow: hidden;
+            transition: border 0.2s ease 0s, color 0.2s ease-out 0s, box-shadow 0.2s ease 0s;
+            border: 1px solid #eaeaea;
+            border-radius: 5px;
+            padding: 0 4pt 0 8pt;
+            height: calc(1.688 * 16pt);
+            min-width: 10rem;
+            background-color: #fff;
+          }
+          
+          .select:hover {
+            border-color: #000;
+          }
+          
+          .select:hover .icon {
+            color: #000
+          }
+          
+          .value {
+            display: inline-flex;
+            flex: 1;
+            height: 100%;
+            align-items: center;
+            line-height: 1;
+            padding: 0;
+            margin-right: 1.25rem;
+            font-size: .875rem;
+            color: #000;
+            width: calc(100% - 1.25rem);
+          }
+          
+          .value &gt; :global(div), .value &gt; :global(div:hover) {
+            border-radius: 0;
+            background-color: transparent;
+            padding: 0;
+            margin: 0;
+            color: inherit;
+          }
+          
+          .placeholder {
+            color: #999;
+          }
+          
+          .icon {
+            position: absolute;
+            right: 4pt;
+            font-size: .875rem;
+            top: 50%;
+            bottom: 0;
+            transform: translateY(-50%) rotate(0deg);
+            pointer-events: none;
+            transition: transform 200ms ease;
+            display: flex;
+            align-items: center;
+            color: #666;
+          }
+        ",
+              "next": null,
+              "parent": [Circular],
+              "prev": null,
+              "type": "text",
+            },
+          ],
+          "name": "style",
+          "namespace": "http://www.w3.org/1999/xhtml",
+          "next": null,
+          "parent": [Circular],
+          "prev": [Circular],
+          "type": "style",
+          "x-attribsNamespace": Object {},
+          "x-attribsPrefix": Object {},
+        },
+        "parent": [Circular],
+        "prev": null,
+        "type": "tag",
+        "x-attribsNamespace": Object {
+          "class": undefined,
+        },
+        "x-attribsPrefix": Object {
+          "class": undefined,
+        },
+      },
+      Object {
+        "attribs": Object {},
+        "children": Array [
+          Object {
+            "data": "
+          .select {
+            display: inline-flex;
+            align-items: center;
+            user-select: none;
+            white-space: nowrap;
+            position: relative;
+            cursor: pointer;
+            max-width: 80vw;
+            width: initial;
+            overflow: hidden;
+            transition: border 0.2s ease 0s, color 0.2s ease-out 0s, box-shadow 0.2s ease 0s;
+            border: 1px solid #eaeaea;
+            border-radius: 5px;
+            padding: 0 4pt 0 8pt;
+            height: calc(1.688 * 16pt);
+            min-width: 10rem;
+            background-color: #fff;
+          }
+          
+          .select:hover {
+            border-color: #000;
+          }
+          
+          .select:hover .icon {
+            color: #000
+          }
+          
+          .value {
+            display: inline-flex;
+            flex: 1;
+            height: 100%;
+            align-items: center;
+            line-height: 1;
+            padding: 0;
+            margin-right: 1.25rem;
+            font-size: .875rem;
+            color: #000;
+            width: calc(100% - 1.25rem);
+          }
+          
+          .value &gt; :global(div), .value &gt; :global(div:hover) {
+            border-radius: 0;
+            background-color: transparent;
+            padding: 0;
+            margin: 0;
+            color: inherit;
+          }
+          
+          .placeholder {
+            color: #999;
+          }
+          
+          .icon {
+            position: absolute;
+            right: 4pt;
+            font-size: .875rem;
+            top: 50%;
+            bottom: 0;
+            transform: translateY(-50%) rotate(0deg);
+            pointer-events: none;
+            transition: transform 200ms ease;
+            display: flex;
+            align-items: center;
+            color: #666;
+          }
+        ",
+            "next": null,
+            "parent": [Circular],
+            "prev": null,
+            "type": "text",
+          },
+        ],
+        "name": "style",
+        "namespace": "http://www.w3.org/1999/xhtml",
+        "next": null,
+        "parent": [Circular],
+        "prev": Object {
+          "attribs": Object {
+            "class": "value placeholder",
+          },
+          "children": Array [],
+          "name": "span",
+          "namespace": "http://www.w3.org/1999/xhtml",
+          "next": [Circular],
+          "parent": [Circular],
+          "prev": null,
+          "type": "tag",
+          "x-attribsNamespace": Object {
+            "class": undefined,
+          },
+          "x-attribsPrefix": Object {
+            "class": undefined,
+          },
+        },
+        "type": "style",
+        "x-attribsNamespace": Object {},
+        "x-attribsPrefix": Object {},
+      },
+    ],
+    "name": "div",
+    "namespace": "http://www.w3.org/1999/xhtml",
+    "next": null,
+    "parent": null,
+    "prev": null,
+    "root": Object {
+      "attribs": Object {},
+      "children": Array [
+        [Circular],
+      ],
+      "name": "root",
+      "namespace": "http://www.w3.org/1999/xhtml",
+      "next": null,
+      "parent": null,
+      "prev": null,
+      "type": "root",
+      "x-attribsNamespace": Object {},
+      "x-attribsPrefix": Object {},
+    },
+    "type": "tag",
+    "x-attribsNamespace": Object {
+      "class": undefined,
+    },
+    "x-attribsPrefix": Object {
+      "class": undefined,
+    },
+  },
+  "_root": [Circular],
+  "length": 1,
+  "options": Object {
+    "decodeEntities": true,
+    "normalizeWhitespace": false,
+    "withDomLvl1": true,
+    "xml": false,
+  },
+}
+`;
+
+exports[`Select should work with different icons 2`] = `
+initialize {
+  "0": Object {
+    "attribs": Object {
+      "class": "select ",
+    },
+    "children": Array [
+      Object {
+        "attribs": Object {
+          "class": "value placeholder",
+        },
+        "children": Array [],
+        "name": "span",
+        "namespace": "http://www.w3.org/1999/xhtml",
+        "next": Object {
+          "attribs": Object {
+            "class": "icon",
+          },
+          "children": Array [
+            Object {
+              "attribs": Object {},
+              "children": Array [
+                Object {
+                  "data": "icon",
+                  "next": null,
+                  "parent": [Circular],
+                  "prev": null,
+                  "type": "text",
+                },
+              ],
+              "name": "span",
+              "namespace": "http://www.w3.org/1999/xhtml",
+              "next": null,
+              "parent": [Circular],
+              "prev": null,
+              "type": "tag",
+              "x-attribsNamespace": Object {},
+              "x-attribsPrefix": Object {},
+            },
+          ],
+          "name": "div",
+          "namespace": "http://www.w3.org/1999/xhtml",
+          "next": Object {
+            "attribs": Object {},
+            "children": Array [
+              Object {
+                "data": "
+          .select {
+            display: inline-flex;
+            align-items: center;
+            user-select: none;
+            white-space: nowrap;
+            position: relative;
+            cursor: pointer;
+            max-width: 80vw;
+            width: initial;
+            overflow: hidden;
+            transition: border 0.2s ease 0s, color 0.2s ease-out 0s, box-shadow 0.2s ease 0s;
+            border: 1px solid #eaeaea;
+            border-radius: 5px;
+            padding: 0 4pt 0 8pt;
+            height: calc(1.688 * 16pt);
+            min-width: 10rem;
+            background-color: #fff;
+          }
+          
+          .select:hover {
+            border-color: #000;
+          }
+          
+          .select:hover .icon {
+            color: #000
+          }
+          
+          .value {
+            display: inline-flex;
+            flex: 1;
+            height: 100%;
+            align-items: center;
+            line-height: 1;
+            padding: 0;
+            margin-right: 1.25rem;
+            font-size: .875rem;
+            color: #000;
+            width: calc(100% - 1.25rem);
+          }
+          
+          .value &gt; :global(div), .value &gt; :global(div:hover) {
+            border-radius: 0;
+            background-color: transparent;
+            padding: 0;
+            margin: 0;
+            color: inherit;
+          }
+          
+          .placeholder {
+            color: #999;
+          }
+          
+          .icon {
+            position: absolute;
+            right: 4pt;
+            font-size: .875rem;
+            top: 50%;
+            bottom: 0;
+            transform: translateY(-50%) rotate(0deg);
+            pointer-events: none;
+            transition: transform 200ms ease;
+            display: flex;
+            align-items: center;
+            color: #666;
+          }
+        ",
+                "next": null,
+                "parent": [Circular],
+                "prev": null,
+                "type": "text",
+              },
+            ],
+            "name": "style",
+            "namespace": "http://www.w3.org/1999/xhtml",
+            "next": null,
+            "parent": [Circular],
+            "prev": [Circular],
+            "type": "style",
+            "x-attribsNamespace": Object {},
+            "x-attribsPrefix": Object {},
+          },
+          "parent": [Circular],
+          "prev": [Circular],
+          "type": "tag",
+          "x-attribsNamespace": Object {
+            "class": undefined,
+          },
+          "x-attribsPrefix": Object {
+            "class": undefined,
+          },
+        },
+        "parent": [Circular],
+        "prev": null,
+        "type": "tag",
+        "x-attribsNamespace": Object {
+          "class": undefined,
+        },
+        "x-attribsPrefix": Object {
+          "class": undefined,
+        },
+      },
+      Object {
+        "attribs": Object {
+          "class": "icon",
+        },
+        "children": Array [
+          Object {
+            "attribs": Object {},
+            "children": Array [
+              Object {
+                "data": "icon",
+                "next": null,
+                "parent": [Circular],
+                "prev": null,
+                "type": "text",
+              },
+            ],
+            "name": "span",
+            "namespace": "http://www.w3.org/1999/xhtml",
+            "next": null,
+            "parent": [Circular],
+            "prev": null,
+            "type": "tag",
+            "x-attribsNamespace": Object {},
+            "x-attribsPrefix": Object {},
+          },
+        ],
+        "name": "div",
+        "namespace": "http://www.w3.org/1999/xhtml",
+        "next": Object {
+          "attribs": Object {},
+          "children": Array [
+            Object {
+              "data": "
+          .select {
+            display: inline-flex;
+            align-items: center;
+            user-select: none;
+            white-space: nowrap;
+            position: relative;
+            cursor: pointer;
+            max-width: 80vw;
+            width: initial;
+            overflow: hidden;
+            transition: border 0.2s ease 0s, color 0.2s ease-out 0s, box-shadow 0.2s ease 0s;
+            border: 1px solid #eaeaea;
+            border-radius: 5px;
+            padding: 0 4pt 0 8pt;
+            height: calc(1.688 * 16pt);
+            min-width: 10rem;
+            background-color: #fff;
+          }
+          
+          .select:hover {
+            border-color: #000;
+          }
+          
+          .select:hover .icon {
+            color: #000
+          }
+          
+          .value {
+            display: inline-flex;
+            flex: 1;
+            height: 100%;
+            align-items: center;
+            line-height: 1;
+            padding: 0;
+            margin-right: 1.25rem;
+            font-size: .875rem;
+            color: #000;
+            width: calc(100% - 1.25rem);
+          }
+          
+          .value &gt; :global(div), .value &gt; :global(div:hover) {
+            border-radius: 0;
+            background-color: transparent;
+            padding: 0;
+            margin: 0;
+            color: inherit;
+          }
+          
+          .placeholder {
+            color: #999;
+          }
+          
+          .icon {
+            position: absolute;
+            right: 4pt;
+            font-size: .875rem;
+            top: 50%;
+            bottom: 0;
+            transform: translateY(-50%) rotate(0deg);
+            pointer-events: none;
+            transition: transform 200ms ease;
+            display: flex;
+            align-items: center;
+            color: #666;
+          }
+        ",
+              "next": null,
+              "parent": [Circular],
+              "prev": null,
+              "type": "text",
+            },
+          ],
+          "name": "style",
+          "namespace": "http://www.w3.org/1999/xhtml",
+          "next": null,
+          "parent": [Circular],
+          "prev": [Circular],
+          "type": "style",
+          "x-attribsNamespace": Object {},
+          "x-attribsPrefix": Object {},
+        },
+        "parent": [Circular],
+        "prev": Object {
+          "attribs": Object {
+            "class": "value placeholder",
+          },
+          "children": Array [],
+          "name": "span",
+          "namespace": "http://www.w3.org/1999/xhtml",
+          "next": [Circular],
+          "parent": [Circular],
+          "prev": null,
+          "type": "tag",
+          "x-attribsNamespace": Object {
+            "class": undefined,
+          },
+          "x-attribsPrefix": Object {
+            "class": undefined,
+          },
+        },
+        "type": "tag",
+        "x-attribsNamespace": Object {
+          "class": undefined,
+        },
+        "x-attribsPrefix": Object {
+          "class": undefined,
+        },
+      },
+      Object {
+        "attribs": Object {},
+        "children": Array [
+          Object {
+            "data": "
+          .select {
+            display: inline-flex;
+            align-items: center;
+            user-select: none;
+            white-space: nowrap;
+            position: relative;
+            cursor: pointer;
+            max-width: 80vw;
+            width: initial;
+            overflow: hidden;
+            transition: border 0.2s ease 0s, color 0.2s ease-out 0s, box-shadow 0.2s ease 0s;
+            border: 1px solid #eaeaea;
+            border-radius: 5px;
+            padding: 0 4pt 0 8pt;
+            height: calc(1.688 * 16pt);
+            min-width: 10rem;
+            background-color: #fff;
+          }
+          
+          .select:hover {
+            border-color: #000;
+          }
+          
+          .select:hover .icon {
+            color: #000
+          }
+          
+          .value {
+            display: inline-flex;
+            flex: 1;
+            height: 100%;
+            align-items: center;
+            line-height: 1;
+            padding: 0;
+            margin-right: 1.25rem;
+            font-size: .875rem;
+            color: #000;
+            width: calc(100% - 1.25rem);
+          }
+          
+          .value &gt; :global(div), .value &gt; :global(div:hover) {
+            border-radius: 0;
+            background-color: transparent;
+            padding: 0;
+            margin: 0;
+            color: inherit;
+          }
+          
+          .placeholder {
+            color: #999;
+          }
+          
+          .icon {
+            position: absolute;
+            right: 4pt;
+            font-size: .875rem;
+            top: 50%;
+            bottom: 0;
+            transform: translateY(-50%) rotate(0deg);
+            pointer-events: none;
+            transition: transform 200ms ease;
+            display: flex;
+            align-items: center;
+            color: #666;
+          }
+        ",
+            "next": null,
+            "parent": [Circular],
+            "prev": null,
+            "type": "text",
+          },
+        ],
+        "name": "style",
+        "namespace": "http://www.w3.org/1999/xhtml",
+        "next": null,
+        "parent": [Circular],
+        "prev": Object {
+          "attribs": Object {
+            "class": "icon",
+          },
+          "children": Array [
+            Object {
+              "attribs": Object {},
+              "children": Array [
+                Object {
+                  "data": "icon",
+                  "next": null,
+                  "parent": [Circular],
+                  "prev": null,
+                  "type": "text",
+                },
+              ],
+              "name": "span",
+              "namespace": "http://www.w3.org/1999/xhtml",
+              "next": null,
+              "parent": [Circular],
+              "prev": null,
+              "type": "tag",
+              "x-attribsNamespace": Object {},
+              "x-attribsPrefix": Object {},
+            },
+          ],
+          "name": "div",
+          "namespace": "http://www.w3.org/1999/xhtml",
+          "next": [Circular],
+          "parent": [Circular],
+          "prev": Object {
+            "attribs": Object {
+              "class": "value placeholder",
+            },
+            "children": Array [],
+            "name": "span",
+            "namespace": "http://www.w3.org/1999/xhtml",
+            "next": [Circular],
+            "parent": [Circular],
+            "prev": null,
+            "type": "tag",
+            "x-attribsNamespace": Object {
+              "class": undefined,
+            },
+            "x-attribsPrefix": Object {
+              "class": undefined,
+            },
+          },
+          "type": "tag",
+          "x-attribsNamespace": Object {
+            "class": undefined,
+          },
+          "x-attribsPrefix": Object {
+            "class": undefined,
+          },
+        },
+        "type": "style",
+        "x-attribsNamespace": Object {},
+        "x-attribsPrefix": Object {},
+      },
+    ],
+    "name": "div",
+    "namespace": "http://www.w3.org/1999/xhtml",
+    "next": null,
+    "parent": null,
+    "prev": null,
+    "root": Object {
+      "attribs": Object {},
+      "children": Array [
+        [Circular],
+      ],
+      "name": "root",
+      "namespace": "http://www.w3.org/1999/xhtml",
+      "next": null,
+      "parent": null,
+      "prev": null,
+      "type": "root",
+      "x-attribsNamespace": Object {},
+      "x-attribsPrefix": Object {},
+    },
+    "type": "tag",
+    "x-attribsNamespace": Object {
+      "class": undefined,
+    },
+    "x-attribsPrefix": Object {
+      "class": undefined,
+    },
+  },
+  "_root": [Circular],
+  "length": 1,
+  "options": Object {
+    "decodeEntities": true,
+    "normalizeWhitespace": false,
+    "withDomLvl1": true,
+    "xml": false,
+  },
+}
+`;
diff --git a/components/select/__tests__/index.test.tsx b/components/select/__tests__/index.test.tsx
new file mode 100644
index 0000000..383e2ff
--- /dev/null
+++ b/components/select/__tests__/index.test.tsx
@@ -0,0 +1,125 @@
+import React from 'react'
+import { mount, render } from 'enzyme'
+import { Select } from 'components'
+import { nativeEvent, updateWrapper } from 'tests/utils'
+
+describe('Select', () => {
+  it('should render correctly', () => {
+    const wrapper = mount(
+      <div>
+        <Select />
+        <Select size="mini" />
+        <Select size="small" />
+        <Select size="large" />
+      </div>
+    )
+    expect(wrapper.html()).toMatchSnapshot()
+    expect(() => wrapper.unmount()).not.toThrow()
+  })
+
+  it('should work with different icons', () => {
+    const MockIcon = () => <span>icon</span>
+    const pure = render(<Select pure />)
+    expect(pure).toMatchSnapshot()
+
+    const icon = render(<Select icon={MockIcon} />)
+    expect(icon).toMatchSnapshot()
+  })
+
+  it('should render value with initial-value', () => {
+    const wrapper = mount(
+      <Select initialValue="2">
+        <Select.Option value="1">1</Select.Option>
+        <Select.Option value="2">Option 2</Select.Option>
+      </Select>
+    )
+    expect(wrapper.find('.value').text())
+      .toContain('Option 2')
+  })
+
+  it('should trigger events when option changed', async () => {
+    let value = ''
+    const changeHandler = jest.fn()
+      .mockImplementation(val => value = val)
+    const wrapper = mount(
+      <Select onChange={changeHandler}>
+        <Select.Option value="1">1</Select.Option>
+        <Select.Option value="2">Option 2</Select.Option>
+      </Select>
+    )
+    wrapper.find('.select').simulate('click', nativeEvent)
+    wrapper.find('.select-dropdown')
+      .find('.option')
+      .at(0)
+      .simulate('click', nativeEvent)
+    await updateWrapper(wrapper, 350)
+    expect(changeHandler).toHaveBeenCalled()
+    expect(value).toEqual('1')
+    changeHandler.mockRestore()
+  })
+
+  it('should ignore events when disabled', async () => {
+    const changeHandler = jest.fn()
+    const wrapper = mount(
+      <Select onChange={changeHandler} disabled>
+        <Select.Option value="1">1</Select.Option>
+        <Select.Option value="2">Option 2</Select.Option>
+      </Select>
+    )
+    wrapper.find('.select').simulate('click', nativeEvent)
+    expect(wrapper.find('.select-dropdown').length).toBe(0)
+    changeHandler.mockRestore()
+  })
+
+  it('should ignore option when option disabled', async () => {
+    let value = ''
+    const changeHandler = jest.fn()
+      .mockImplementation(val => value = val)
+    const wrapper = mount(
+      <Select onChange={changeHandler}>
+        <Select.Option value="1">1</Select.Option>
+        <Select.Option value="2" disabled>Option 2</Select.Option>
+      </Select>
+    )
+    wrapper.find('.select').simulate('click', nativeEvent)
+    wrapper.find('.select-dropdown')
+      .find('.option')
+      .at(1)
+      .simulate('click', nativeEvent)
+    await updateWrapper(wrapper, 350)
+    expect(changeHandler).not.toHaveBeenCalled()
+    expect(value).not.toEqual('2')
+    changeHandler.mockRestore()
+  })
+
+  it('should be change when value changed', async () => {
+    const wrapper = mount(
+      <Select>
+        <Select.Option value="1">Option 1</Select.Option>
+        <Select.Option value="2">Option 2</Select.Option>
+      </Select>
+    )
+
+    wrapper.setProps({ value: '2' })
+    await updateWrapper(wrapper, 300)
+    expect(wrapper.find('.value').text())
+      .toContain('Option 2')
+
+    wrapper.setProps({ value: '1' })
+    await updateWrapper(wrapper, 300)
+    expect(wrapper.find('.value').text())
+      .toContain('Option 1')
+  })
+
+  it('should be wraning when ident value missing', () => {
+    let errorMessage = ''
+    const errorSpy = jest.spyOn(console, 'error')
+      .mockImplementation(msg => errorMessage = msg)
+    const SelectOption = Select.Option as any
+    const wrapper = mount(<Select><SelectOption>1</SelectOption></Select>)
+    wrapper.find('.select').simulate('click', nativeEvent)
+    
+    expect(errorMessage).toContain('required')
+    errorSpy.mockRestore()
+  })
+})