diff --git a/.dumirc.ts b/.dumirc.ts
new file mode 100644
index 0000000..01854c1
--- /dev/null
+++ b/.dumirc.ts
@@ -0,0 +1,7 @@
+import { defineConfig } from 'dumi';
+
+export default defineConfig({
+ themeConfig: {
+ name: 'Checkbox',
+ },
+});
diff --git a/.fatherrc.js b/.fatherrc.js
deleted file mode 100644
index 571c236..0000000
--- a/.fatherrc.js
+++ /dev/null
@@ -1,10 +0,0 @@
-export default {
- entry: ['src/index.js'],
- cjs: 'babel',
- esm: { type: 'babel', importLibToEs: true },
- preCommit: {
- eslint: true,
- prettier: true,
- },
- runtimeHelpers: true,
-};
diff --git a/.fatherrc.ts b/.fatherrc.ts
new file mode 100644
index 0000000..96268ae
--- /dev/null
+++ b/.fatherrc.ts
@@ -0,0 +1,5 @@
+import { defineConfig } from 'father';
+
+export default defineConfig({
+ plugins: ['@rc-component/father-plugin'],
+});
diff --git a/.gitignore b/.gitignore
index d0036e2..3fa2c9b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,8 +30,13 @@ es
coverage
yarn.lock
+
# dumi
.umi
+.dumi/tmp
+.dumi/tmp-production
.umi-production
.umi-test
-.docs
\ No newline at end of file
+.docs
+
+pnpm-lock.yaml
diff --git a/.husky/pre-commit b/.husky/pre-commit
new file mode 100755
index 0000000..798e8b4
--- /dev/null
+++ b/.husky/pre-commit
@@ -0,0 +1,5 @@
+#!/bin/sh
+. "$(dirname "$0")/_/husky.sh"
+
+npx --no-install lint-staged --quiet
+npx --no-install jest
diff --git a/.umirc.ts b/.umirc.ts
deleted file mode 100644
index 33a8fa4..0000000
--- a/.umirc.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-// more config: https://d.umijs.org/config
-import { defineConfig } from 'dumi';
-
-export default defineConfig({
- title: 'rc-checkbox',
- outputPath: '.docs',
- exportStatic: {},
-});
\ No newline at end of file
diff --git a/docs/demo/simple.md b/docs/demo/simple.md
deleted file mode 100644
index dd7fb36..0000000
--- a/docs/demo/simple.md
+++ /dev/null
@@ -1,3 +0,0 @@
-## Simple
-
-
\ No newline at end of file
diff --git a/docs/demo/simple.tsx b/docs/demo/simple.tsx
new file mode 100644
index 0000000..336ef62
--- /dev/null
+++ b/docs/demo/simple.tsx
@@ -0,0 +1,125 @@
+/* eslint no-console:0, jsx-a11y/label-has-for: 0, jsx-a11y/label-has-associated-control: 0 */
+import React from 'react';
+import Checkbox from 'rc-checkbox';
+import '../../assets/index.less';
+
+import type { CheckboxProps } from 'rc-checkbox';
+
+const onChange = (e: any) => {
+ console.log('Checkbox checked:', e.target.checked);
+};
+
+const onKeyDown: CheckboxProps['onKeyDown'] = (e) => {
+ console.log('Checkbox key down:', e.key);
+};
+
+const onKeyPress: CheckboxProps['onKeyPress'] = (e) => {
+ console.log('Checkbox key press:', e.key);
+};
+
+const onKeyUp: CheckboxProps['onKeyUp'] = (e) => {
+ console.log('Checkbox key up:', e.key);
+};
+
+export default () => {
+ const [disabled, setDisabled] = React.useState(false);
+
+ const toggle = () => {
+ setDisabled((disabled) => !disabled);
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/docs/example.md b/docs/example.md
new file mode 100644
index 0000000..16cf22a
--- /dev/null
+++ b/docs/example.md
@@ -0,0 +1,8 @@
+---
+title: Example
+nav:
+ title: Example
+ path: /example
+---
+
+
diff --git a/docs/examples/simple.jsx b/docs/examples/simple.jsx
deleted file mode 100644
index e201972..0000000
--- a/docs/examples/simple.jsx
+++ /dev/null
@@ -1,139 +0,0 @@
-/* eslint no-console:0, jsx-a11y/label-has-for: 0, jsx-a11y/label-has-associated-control: 0 */
-import React from 'react';
-import Checkbox from 'rc-checkbox';
-import '../../assets/index.less';
-
-function onChange(e) {
- console.log('Checkbox checked:', e.target.checked);
-}
-
-function onKeyDown(e) {
- console.log('Checkbox key down:', e.key);
-}
-
-function onKeyPress(e) {
- console.log('Checkbox key press:', e.key);
-}
-
-function onKeyUp(e) {
- console.log('Checkbox key up:', e.key);
-}
-
-export default class SimpleDemo extends React.Component {
- state = {
- disabled: false,
- };
-
- toggle = () => {
- this.setState(state => ({
- disabled: !state.disabled,
- }));
- };
-
- render() {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
- }
-}
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 0000000..63ea97f
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,28 @@
+---
+hero:
+ title: rc-checkbox
+ description: checkbox ui component for react
+---
+
+## 📦 Install
+
+```bash
+# npm install
+npm install rc-checkbox --save
+
+# yarn install
+yarn add rc-checkbox
+
+# pnpm install
+pnpm i rc-checkbox
+```
+
+## 🔨 Usage
+
+```ts
+import Checkbox from 'rc-checkbox';
+
+export default () => {
+ return ;
+};
+```
diff --git a/jest.config.ts b/jest.config.ts
new file mode 100644
index 0000000..91070fd
--- /dev/null
+++ b/jest.config.ts
@@ -0,0 +1,10 @@
+import { createConfig } from '@umijs/test';
+
+import type { Config } from '@umijs/test';
+
+const config: Config.InitialOptions = {
+ ...createConfig(),
+ testEnvironment: 'jsdom',
+};
+
+export default config;
diff --git a/package.json b/package.json
index 2be075c..51eef9b 100644
--- a/package.json
+++ b/package.json
@@ -8,8 +8,9 @@
"react-checkbox",
"checkbox"
],
- "main": "./lib/index",
- "module": "./es/index",
+ "main": "lib/index.js",
+ "module": "es/index.js",
+ "typings": "es/index.d.ts",
"files": [
"assets/*.css",
"es",
@@ -31,29 +32,48 @@
"gh-pages": "npm run build && father doc deploy -d .docs",
"prepublishOnly": "npm run compile && np --yolo --no-publish",
"lint": "eslint . --ext='jsx'",
- "test": "father test",
- "coverage": "father test --coverage"
+ "test": "jest",
+ "coverage": "jest --coverage",
+ "prepare": "husky install"
},
"dependencies": {
"@babel/runtime": "^7.10.1",
- "classnames": "^2.2.1"
+ "classnames": "^2.3.2",
+ "rc-util": "^5.25.2"
},
"devDependencies": {
- "@types/classnames": "^2.2.10",
- "@types/jest": "^26.0.5",
- "@umijs/fabric": "^2.2.2",
+ "@rc-component/father-plugin": "^1.0.1",
+ "@testing-library/jest-dom": "^5.16.5",
+ "@testing-library/react": "^13.4.0 ",
+ "@testing-library/user-event": "^14.4.3",
+ "@types/classnames": "^2.3.1",
+ "@types/jest": "^29.2.4",
+ "@types/react": "^18.0.26",
+ "@types/react-dom": "^18.0.9",
+ "@umijs/fabric": "^3.0.0",
+ "@umijs/test": "^4.0.34",
"coveralls": "^3.0.6",
- "dumi": "^1.1.37",
- "enzyme": "^3.0.0",
- "enzyme-adapter-react-16": "^1.0.1",
- "enzyme-to-json": "^3.0.0",
+ "dumi": "^2.0.12",
"eslint": "^7.0.0",
- "father": "^2.13.4",
+ "father": "^4.1.1",
+ "husky": "8.0.2",
+ "jest": "^29.3.1",
+ "jest-environment-jsdom": "^29.3.1",
"less": "^3.11.1",
+ "lint-staged": "^13.1.0",
"np": "^6.2.3",
- "react": "^16.0.0",
- "react-dom": "^16.0.0",
- "react-test-renderer": "^16.0.0"
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "ts-node": "^10.9.1",
+ "typescript": "^4.9.4"
+ },
+ "lint-staged": {
+ "*.{js,jsx,less,md,json}": [
+ "prettier --write"
+ ],
+ "*.ts?(x)": [
+ "prettier --parser=typescript --write"
+ ]
},
"peerDependencies": {
"react": ">=16.9.0",
diff --git a/src/index.tsx b/src/index.tsx
index 17a896a..bf7492c 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -64,16 +64,14 @@ export const Checkbox = forwardRef((props, ref) => {
return;
}
- const checked = e.target.checked;
-
if (!('checked' in props)) {
- setRawValue(checked);
+ setRawValue(e.target.checked);
}
onChange?.({
target: {
...props,
- checked,
+ checked: e.target.checked,
},
stopPropagation() {
e.stopPropagation();
diff --git a/tests/index.test.jsx b/tests/index.test.jsx
deleted file mode 100644
index b9ac8ba..0000000
--- a/tests/index.test.jsx
+++ /dev/null
@@ -1,181 +0,0 @@
-import React from 'react';
-import { mount } from 'enzyme';
-import Checkbox from '../src';
-
-describe('rc-checkbox', () => {
- it('works', () => {
- const wrapper = mount();
- expect(wrapper.state('checked')).toBe(false);
- wrapper.find('input').simulate('change', { target: { checked: true } });
- expect(wrapper.state('checked')).toBe(true);
- wrapper.find('input').simulate('change', { target: { checked: false } });
- expect(wrapper.state('checked')).toBe(false);
- });
-
- it('click radio', () => {
- const wrapper = mount();
- expect(wrapper.state('checked')).toBe(false);
- wrapper.find('input').simulate('change', { target: { checked: true } });
- expect(wrapper.state('checked')).toBe(true);
- wrapper.find('input').simulate('change', { target: { checked: true } });
- expect(wrapper.state('checked')).toBe(true);
- });
-
- it('control mode', () => {
- const wrapper = mount();
- expect(wrapper.state('checked')).toBe(true);
- wrapper.find('input').simulate('change', { target: { checked: true } });
- expect(wrapper.state('checked')).toBe(true);
- wrapper.find('input').simulate('change', { target: { checked: false } });
- expect(wrapper.state('checked')).toBe(true);
- });
-
- it('stopPropagation and preventDefault', () => {
- const onChange = jest.fn();
- const wrapper = mount(
-
- {
- e.stopPropagation();
- e.preventDefault();
- }}
- />
-
,
- );
- wrapper.find('input').simulate('change', { target: { checked: true } });
- expect(onChange).not.toHaveBeenCalled();
- });
-
- it('passes data-* props to input', () => {
- const wrapper = mount();
- const renderedInput = wrapper.find('input').instance();
- expect(renderedInput.attributes['data-type'].value).toEqual('my-data-type');
- });
-
- it('passes aria-* props to input', () => {
- const wrapper = mount();
- const renderedInput = wrapper.find('input').instance();
- expect(renderedInput.attributes['aria-label'].value).toEqual('my-aria-label');
- });
-
- it('passes role prop to input', () => {
- // eslint-disable-next-line jsx-a11y/aria-role
- const wrapper = mount();
- const renderedInput = wrapper.find('input').instance();
- expect(renderedInput.attributes.role.value).toEqual('my-role');
- });
-
- it('passes value prop to input', () => {
- const wrapper = mount();
- const renderedInput = wrapper.find('input').instance();
- expect(renderedInput.attributes.value.value).toEqual('my-custom-value');
- });
-
- it('passes number value prop to input', () => {
- const wrapper = mount();
- const renderedInput = wrapper.find('input').instance();
- expect(renderedInput.attributes.value.value).toEqual('6');
- });
-
- it('passes title prop to input', () => {
- const wrapper = mount();
- const renderedInput = wrapper.find('input').instance();
- expect(renderedInput.attributes.title.value).toEqual('my-custom-title');
- });
-
- it('focus()', () => {
- const container = document.createElement('div');
- document.body.appendChild(container);
- const handleFocus = jest.fn();
- const wrapper = mount(, { attachTo: container });
- wrapper.instance().focus();
- expect(handleFocus).toBeCalled();
- });
-
- it('blur()', () => {
- const container = document.createElement('div');
- document.body.appendChild(container);
- const handleBlur = jest.fn();
- const wrapper = mount(, { attachTo: container });
- wrapper.instance().focus();
- wrapper.instance().blur();
- expect(handleBlur).toBeCalled();
- });
-
- it('autoFocus', () => {
- const container = document.createElement('div');
- document.body.appendChild(container);
- const handleFocus = jest.fn();
- mount(, { attachTo: container });
- expect(handleFocus).toBeCalled();
- });
-
- it('onChange', () => {
- const container = document.createElement('div');
- document.body.appendChild(container);
- const onChange = jest.fn();
- const wrapper = mount(, { attachTo: container });
- wrapper.find('input').simulate('change');
- expect(onChange).toBeCalled();
- });
-
- it('onChange disabled', () => {
- const container = document.createElement('div');
- document.body.appendChild(container);
- const onChange = jest.fn();
- const wrapper = mount(, { attachTo: container });
- wrapper.find('input').simulate('change');
- expect(onChange).not.toBeCalled();
- });
-
- it('passes required prop to input', () => {
- const wrapper = mount();
- const renderedInput = wrapper.find('input').getDOMNode();
- expect(renderedInput.hasAttribute('required')).toBe(true);
- });
-
- it('onKeyDown', () => {
- const container = document.createElement('div');
- document.body.appendChild(container);
- const onKeyDown = jest.fn();
- const wrapper = mount(, { attachTo: container });
- wrapper.find('input').simulate('keydown');
- expect(onKeyDown).toBeCalled();
- });
-
- it('onKeyPress', () => {
- const container = document.createElement('div');
- document.body.appendChild(container);
- const onKeyPress = jest.fn();
- const wrapper = mount(, { attachTo: container });
- wrapper.find('input').simulate('keypress');
- expect(onKeyPress).toBeCalled();
- });
-
- it('onKeyUp', () => {
- const container = document.createElement('div');
- document.body.appendChild(container);
- const onKeyUp = jest.fn();
- const wrapper = mount(, { attachTo: container });
- wrapper.find('input').simulate('keyup');
- expect(onKeyUp).toBeCalled();
- });
-
- it('has default keyboard events handler', () => {
- const onKeyDown = jest.fn();
- const onKeyPress = jest.fn();
- const onKeyUp = jest.fn();
-
- const wrapper = mount(
-
-
-
,
- );
- wrapper.find('input').simulate('keydown');
- expect(onKeyDown).toBeCalled();
- wrapper.find('input').simulate('keypress');
- expect(onKeyPress).toBeCalled();
- wrapper.find('input').simulate('keyup');
- expect(onKeyUp).toBeCalled();
- });
-});
diff --git a/tests/index.test.tsx b/tests/index.test.tsx
new file mode 100644
index 0000000..ce49697
--- /dev/null
+++ b/tests/index.test.tsx
@@ -0,0 +1,268 @@
+import * as React from 'react';
+import KeyCode from 'rc-util/lib/KeyCode';
+import { fireEvent, render } from '@testing-library/react';
+import Checkbox from '../src';
+
+import type { CheckboxRef } from '../src';
+
+describe('rc-checkbox', () => {
+ it('works', () => {
+ const { container } = render();
+ const inputEl = container.querySelector('input')!;
+
+ expect(inputEl.checked).toBe(false);
+
+ fireEvent.click(inputEl);
+ expect(inputEl.checked).toBe(true);
+
+ fireEvent.click(inputEl);
+ expect(inputEl.checked).toBe(false);
+ });
+
+ it('click radio', () => {
+ const { container } = render();
+ const inputEl = container.querySelector('input')!;
+
+ expect(inputEl.checked).toBe(false);
+
+ fireEvent.click(inputEl);
+ expect(inputEl.checked).toBe(true);
+
+ fireEvent.click(inputEl);
+ expect(inputEl.checked).toBe(true);
+ });
+
+ it('click checkbox', () => {
+ const { container } = render();
+ const inputEl = container.querySelector('input')!;
+
+ expect(inputEl.checked).toBe(false);
+
+ fireEvent.click(inputEl);
+ expect(inputEl.checked).toBe(true);
+
+ fireEvent.click(inputEl);
+ expect(inputEl.checked).toBe(false);
+ });
+
+ it('control mode', () => {
+ const { container } = render();
+ const inputEl = container.querySelector('input')!;
+
+ expect(inputEl.checked).toBe(true);
+
+ fireEvent.click(inputEl);
+ expect(inputEl.checked).toBe(true);
+
+ fireEvent.click(inputEl);
+ expect(inputEl.checked).toBe(true);
+ });
+
+ it('stopPropagation and preventDefault', () => {
+ const onChange = jest.fn();
+
+ const { container } = render(
+
+ {
+ e.stopPropagation();
+ e.preventDefault();
+ }}
+ />
+
,
+ );
+
+ const inputEl = container.querySelector('input')!;
+
+ fireEvent.click(inputEl);
+ expect(onChange).not.toHaveBeenCalled();
+ });
+
+ it('passes data-* props to input', () => {
+ const { container } = render();
+ const inputEl = container.querySelector('input')!;
+
+ expect(inputEl.getAttribute('data-type')).toEqual('my-data-type');
+ });
+
+ it('passes aria-* props to input', () => {
+ const { container } = render();
+ const inputEl = container.querySelector('input')!;
+
+ expect(inputEl.getAttribute('aria-label')).toEqual('my-aria-label');
+ });
+
+ it('passes role prop to input', () => {
+ // eslint-disable-next-line jsx-a11y/aria-role
+ const { container } = render();
+ const inputEl = container.querySelector('input')!;
+
+ // @ts-ignore
+ expect(inputEl.attributes.role.value).toEqual('my-role');
+ });
+
+ it('passes value prop to input', () => {
+ const { container } = render();
+ const inputEl = container.querySelector('input')!;
+
+ // @ts-ignore
+ expect(inputEl.attributes.value.value).toEqual('my-custom-value');
+ });
+
+ it('passes number value prop to input', () => {
+ const { container } = render();
+ const inputEl = container.querySelector('input')!;
+
+ // @ts-ignore
+ expect(inputEl.attributes.value.value).toEqual('6');
+ });
+
+ it('passes title prop to input', () => {
+ const { container } = render();
+ const inputEl = container.querySelector('input')!;
+
+ // @ts-ignore
+ expect(inputEl.attributes.title.value).toEqual('my-custom-title');
+ });
+
+ it('onFocus', () => {
+ const onFocus = jest.fn();
+
+ const { container } = render();
+ const inputEl = container.querySelector('input')!;
+
+ fireEvent.focus(inputEl);
+
+ expect(onFocus).toHaveBeenCalledTimes(1);
+ });
+
+ it('onBlur', () => {
+ const onBlur = jest.fn();
+
+ const { container } = render();
+ const inputEl = container.querySelector('input')!;
+
+ fireEvent.focus(inputEl);
+ fireEvent.blur(inputEl);
+
+ expect(onBlur).toHaveBeenCalledTimes(1);
+ });
+
+ it('autoFocus', () => {
+ const handleFocus = jest.fn();
+
+ render();
+
+ expect(handleFocus).toHaveBeenCalledTimes(1);
+ });
+
+ it('onChange', () => {
+ const onChange = jest.fn();
+
+ const { container } = render();
+ const inputEl = container.querySelector('input')!;
+
+ fireEvent.click(inputEl);
+
+ expect(onChange).toHaveBeenCalledTimes(1);
+ expect(inputEl.checked).toBe(true);
+ });
+
+ it('onChange disabled', () => {
+ const onChange = jest.fn();
+
+ const { container } = render();
+ const input = container.querySelector('input')!;
+
+ fireEvent.click(input);
+
+ expect(onChange).not.toHaveBeenCalled();
+ });
+
+ it('passes required prop to input', () => {
+ const { container } = render();
+
+ const inputEl = container.querySelector('input')!;
+ expect(inputEl.hasAttribute('required')).toBe(true);
+ });
+
+ it('onKeyDown', () => {
+ const onKeyDown = jest.fn();
+
+ const { container } = render();
+ const inputEl = container.querySelector('input')!;
+
+ fireEvent.keyDown(inputEl, { which: KeyCode.TAB });
+
+ expect(onKeyDown).toHaveBeenCalledTimes(1);
+ });
+
+ it('onKeyPress', () => {
+ const onKeyPress = jest.fn();
+
+ const { container } = render();
+ const inputEl = container.querySelector('input')!;
+
+ // https://github.com/testing-library/react-testing-library/issues/269
+ fireEvent.keyPress(inputEl, { key: 'Enter', code: 13, charCode: 13 });
+
+ expect(onKeyPress).toHaveBeenCalledTimes(1);
+ });
+
+ it('onKeyUp', () => {
+ const onKeyUp = jest.fn();
+
+ const { container } = render();
+ const inputEl = container.querySelector('input')!;
+
+ fireEvent.keyUp(inputEl, { which: KeyCode.TAB });
+
+ expect(onKeyUp).toHaveBeenCalledTimes(1);
+ });
+
+ it('has default keyboard events handler', () => {
+ const onKeyDown = jest.fn();
+ const onKeyPress = jest.fn();
+ const onKeyUp = jest.fn();
+
+ const { container } = render(
+
+
+
,
+ );
+ const inputEl = container.querySelector('input')!;
+
+ fireEvent.keyDown(inputEl, { which: KeyCode.TAB });
+ expect(onKeyDown).toHaveBeenCalledTimes(1);
+
+ fireEvent.keyPress(inputEl, { key: 'Enter', code: 13, charCode: 13 });
+ expect(onKeyPress).toHaveBeenCalledTimes(1);
+
+ fireEvent.keyUp(inputEl, { which: KeyCode.TAB });
+ expect(onKeyUp).toHaveBeenCalledTimes(1);
+ });
+});
+
+describe('Checkbox ref', () => {
+ it('focus and blur should work', () => {
+ const ref = React.createRef();
+
+ const { container } = render();
+ const inputEl = container.querySelector('input')!;
+
+ ref.current?.focus();
+ expect(document.activeElement).toBe(inputEl);
+
+ ref.current?.blur();
+ expect(document.activeElement).not.toBe(inputEl);
+ });
+
+ it('input should work', () => {
+ const ref = React.createRef();
+
+ const { container } = render();
+ const inputEl = container.querySelector('input')!;
+
+ expect(ref.current?.input).toBe(inputEl);
+ });
+});