Browse Source

[add] unit test for button, drawer, select box and text field

RoyLiu 5 years ago
parent
commit
7779b4c00e

+ 8 - 0
__mocks__/next-i18next.js

@@ -0,0 +1,8 @@
+const React = require('react');
+
+const NextI18Next = function NextI18Next() {
+  this.withTranslation = () => Component => props => <Component t={k => k} {...props} />,
+  this.Link = ({ children }) => <div>{children}</div>;
+};
+
+module.exports = NextI18Next;

+ 52 - 0
__mocks__/react-i18next.js

@@ -0,0 +1,52 @@
+const React = require('react');
+const reactI18next = require('react-i18next');
+
+const hasChildren = node => node && (node.children || (node.props && node.props.children));
+
+const getChildren = node => (
+  node && node.children ? node.children : node.props && node.props.children
+);
+
+const renderNodes = (reactNodes) => {
+  if (typeof reactNodes === 'string') {
+    return reactNodes;
+  }
+
+  return Object.keys(reactNodes).map((key, i) => {
+    const child = reactNodes[key];
+    const isElement = React.isValidElement(child);
+
+    if (typeof child === 'string') {
+      return child;
+    } if (hasChildren(child)) {
+      const indexKey = `key_${i}`;
+      const inner = renderNodes(getChildren(child));
+      return React.cloneElement(
+        child,
+        { ...child.props, key: indexKey },
+        inner,
+      );
+    } if (typeof child === 'object' && !isElement) {
+      return Object.keys(child).reduce((str, childKey) => `${str}${child[childKey]}`, '');
+    }
+
+    return child;
+  });
+};
+
+module.exports = {
+  // this mock makes sure any components using the translate HoC receive the t function as a prop
+  withTranslation: () => Component => props => <Component t={k => k} {...props} />,
+  Trans: ({ children }) => renderNodes(children),
+  NamespacesConsumer: ({ children }) => children(k => k, { i18n: {} }),
+
+  // mock if needed
+  Interpolate: reactI18next.Interpolate,
+  I18nextProvider: reactI18next.I18nextProvider,
+  loadNamespaces: reactI18next.loadNamespaces,
+  reactI18nextModule: reactI18next.reactI18nextModule,
+  setDefaults: reactI18next.setDefaults,
+  getDefaults: reactI18next.getDefaults,
+  setI18n: reactI18next.setI18n,
+  getI18n: reactI18next.getI18n,
+};

+ 45 - 0
__test__/button.test.tsx

@@ -0,0 +1,45 @@
+/* eslint-disable import/no-extraneous-dependencies */
+/* eslint-disable no-undef */
+import React from 'react';
+import { render, cleanup, fireEvent } from '@testing-library/react';
+
+import Button from '../components/Button';
+import { color } from '../constants/style';
+
+describe('Button component', () => {
+  afterEach(cleanup);
+
+  test('check button content', () => {
+    const { getByText } = render(<Button>btn content</Button>);
+
+    expect(getByText('btn content').textContent).toBe('btn content');
+  });
+
+  test('check button theme', () => {
+    const { container } = render(<Button appearance="primary">btn content</Button>);
+    const btn = container.querySelector('button');
+    const style = window.getComputedStyle(btn as HTMLElement) as { [key: string]: any };
+
+    expect(style['border-color']).toBe(color.primary);
+    expect(style['background-color']).toBe('rgb(88, 106, 242)');
+    expect(style.color).toBe('white');
+  });
+
+  test('click button', () => {
+    let counter = 0;
+    const cb = (): void => { counter += 1; };
+    const { getByText } = render(<Button onClick={cb}>btn content</Button>);
+
+    fireEvent.click(getByText('btn content'));
+    expect(counter).toBe(1);
+  });
+
+  test('disable button', () => {
+    let counter = 0;
+    const cb = (): void => { counter += 1; };
+    const { getByText } = render(<Button isDisabled onClick={cb}>btn content</Button>);
+
+    fireEvent.click(getByText('btn content'));
+    expect(counter).toBe(0);
+  });
+});

+ 31 - 0
__test__/drawer.test.tsx

@@ -0,0 +1,31 @@
+/* eslint-disable import/no-extraneous-dependencies */
+/* eslint-disable no-undef */
+import React from 'react';
+import { render, cleanup } from '@testing-library/react';
+
+import Drawer from '../components/Drawer';
+
+describe('Drawer component', () => {
+  afterEach(cleanup);
+
+  test('drawer content', () => {
+    const { getByText } = render(<Drawer>content</Drawer>);
+    expect(getByText('content').textContent).toBe('content');
+  });
+
+  test('close drawer', () => {
+    const { getByTestId } = render(<Drawer>content</Drawer>);
+    const ele = getByTestId('drawer');
+    const style = window.getComputedStyle(ele as HTMLElement) as { [key: string]: any };
+
+    expect(style.transform).toBe('translate(0,267px)');
+  });
+
+  test('open drawer', () => {
+    const { getByTestId } = render(<Drawer open>content</Drawer>);
+    const ele = getByTestId('drawer');
+    const style = window.getComputedStyle(ele as HTMLElement) as { [key: string]: any };
+
+    expect(style.transform).toBe('translate(0,0)');
+  });
+});

+ 45 - 0
__test__/selectBox.test.tsx

@@ -0,0 +1,45 @@
+/* eslint-disable import/no-extraneous-dependencies */
+/* eslint-disable no-undef */
+import React from 'react';
+import { render, cleanup, fireEvent } from '@testing-library/react';
+
+import SelectBox from '../components/SelectBox';
+
+describe('SelectBox component', () => {
+  afterEach(cleanup);
+
+  const options = [
+    {
+      key: 1,
+      content: 'option 1',
+      value: 1,
+    },
+    {
+      key: 2,
+      content: 'option 2',
+      value: 2,
+    },
+  ];
+
+  test('default value', () => {
+    const { getAllByTestId } = render(<SelectBox options={options} />);
+    const ele = getAllByTestId('selected')[0].querySelector('div') as HTMLDivElement;
+
+    expect(ele.textContent).toBe('option 1');
+  });
+
+  test('open list', () => {
+    const { container } = render(<SelectBox options={options} />);
+
+    fireEvent.mouseDown(container.querySelector('[data-testid="selected"]') as HTMLElement);
+
+    expect(container.querySelectorAll('span')[1].textContent).toBe('option 2');
+  });
+
+  test('use input box', () => {
+    const { container } = render(<SelectBox options={options} useInput />);
+    const inputNode = container.querySelector('input') as HTMLInputElement;
+
+    expect(inputNode.value).toBe('option 1');
+  });
+});

+ 30 - 0
__test__/textField.test.tsx

@@ -0,0 +1,30 @@
+/* eslint-disable import/no-extraneous-dependencies */
+/* eslint-disable no-undef */
+import React from 'react';
+import { render, cleanup, fireEvent } from '@testing-library/react';
+
+import TextField from '../components/TextField';
+
+describe('TextField component', () => {
+  afterEach(cleanup);
+
+  test('input element', () => {
+    const { container } = render(<TextField />);
+    const inputNode = container.querySelector('input') as HTMLElement;
+    expect(inputNode.tagName).toBe('INPUT');
+  });
+
+  test('textarea element', () => {
+    const { container } = render(<TextField variant="multiline" />);
+    const textAreaNode = container.querySelector('TextArea') as HTMLElement;
+    expect(textAreaNode.tagName).toBe('TEXTAREA');
+  });
+
+  test('onChange event', () => {
+    const { container } = render(<TextField />);
+    const inputNode = container.querySelector('input') as HTMLInputElement;
+    fireEvent.change(inputNode, { target: { value: 'text' } });
+
+    expect(inputNode.value).toBe('text');
+  });
+});

+ 3 - 2
components/Drawer/index.tsx

@@ -7,18 +7,19 @@ import { Slide, Container } from './styled';
 type Props = {
   anchor?: 'left' | 'top' | 'right' |'bottom';
   children: React.ReactNode;
-  open: boolean;
+  open?: boolean;
 };
 
 const Drawer: React.FunctionComponent<Props> = ({
   anchor = 'bottom',
   children,
-  open,
+  open = false,
 }: Props) => (
   <Portal>
     <Slide
       open={open}
       anchor={anchor}
+      data-testid="drawer"
     >
       <Container>
         {children}

+ 2 - 1
components/SelectBox/index.tsx

@@ -94,6 +94,7 @@ const SelectBox: React.FunctionComponent<Props> = ({
         onMouseDown={handleClick}
         onBlur={handleBlur}
         tabIndex={0}
+        data-testid="selected"
       >
         {
           useInput && (typeof value === 'string' || typeof value === 'number') ? (
@@ -113,7 +114,7 @@ const SelectBox: React.FunctionComponent<Props> = ({
       </Selected>
       {
         !isCollapse ? (
-          <OptionWrapper ref={optionRef}>
+          <OptionWrapper ref={optionRef} data-testid="option-list">
             {
               options.map((option: SelectOptionType, index: number) => (
                 <Option

+ 10 - 3
config/jest.config.js

@@ -1,9 +1,16 @@
 module.exports = {
   rootDir: '../',
+  testMatch: [
+    '**/__tests__/**/*.+(ts|tsx|js)',
+    '**/?(*.)+(spec|test).+(ts|tsx|js)',
+  ],
   transform: {
-    '\\.(js|jsx)?$': 'babel-jest',
+    '\\.(ts|tsx|js|jsx)?$': 'babel-jest',
+    '^.+\\.(ts|tsx)$': 'ts-jest',
   },
-  testMatch: ['<rootDir>/__test__/(*.)test.{js, jsx}'],
-  moduleFileExtensions: ['js', 'jsx', 'json'],
+  setupFilesAfterEnv: [
+    '@testing-library/jest-dom/extend-expect',
+  ],
+  moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],
   testPathIgnorePatterns: ['<rootDir>/.next/', '<rootDir>/node_modules/'],
 };

+ 2 - 1
package.json

@@ -59,11 +59,12 @@
     "eslint-plugin-prettier": "^3.1.1",
     "eslint-plugin-react": "^7.11.1",
     "eslint-plugin-react-hooks": "^2.2.0",
-    "jest": "^25.0.0",
+    "jest": "24.9.0",
     "jest-styled-components": "^6.2.1",
     "pre-commit": "^1.2.2",
     "prettier": "^1.19.1",
     "react-test-renderer": "^16.5.2",
+    "ts-jest": "^24.2.0",
     "typescript": "^3.7.2",
     "webpack-cli": "^3.2.3",
     "webpack-node-externals": "^1.7.2"

File diff suppressed because it is too large
+ 578 - 630
yarn.lock