Skip to content

Commit

Permalink
refactor(MixinUiState): convert to TypeScript, improve tests
Browse files Browse the repository at this point in the history
  • Loading branch information
YSMJ1994 authored and eternalsky committed Jan 23, 2024
1 parent 2c5d16a commit 86e5414
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 31 deletions.
42 changes: 23 additions & 19 deletions components/mixin-ui-state/__tests__/index-spec.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import React from 'react';
import Enzyme, { mount } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import assert from 'power-assert';
import UIState from '../index';
import React, { ReactElement, cloneElement } from 'react';
import { MountReturn } from 'cypress/react';
import UIState, { UIStateProps } from '../index';

/* eslint-disable */

Enzyme.configure({ adapter: new Adapter() });
interface TestProps extends UIStateProps {
reset?: boolean;
}

class Test extends UIState {
componentWillReceiveProps(nextProps) {
class Test extends UIState<TestProps> {
componentWillReceiveProps(nextProps: TestProps) {
if (nextProps.reset) {
this.resetUIState();
}
Expand All @@ -21,21 +19,27 @@ class Test extends UIState {
}

describe('mixin-ui-state', () => {
const wrapper = mount(<Test id="abc" onMouseEnter={() => {}} />);

it('Focus element should has focused class', () => {
wrapper.find('input').simulate('focus');
assert(wrapper.find('.focused').length === 1);
cy.mount(<Test />);
cy.get('input').focus();
cy.get('.focused').should('exist');
});

it('Blur element should not has focused class', () => {
wrapper.find('input').simulate('blur');
assert(wrapper.find('.focused').length === 0);
cy.mount(<Test />);
cy.get('input').focus();
cy.get('.focused').should('exist');
cy.get('input').blur();
cy.get('.focused').should('not.exist');
});

it('resetUIState should not has focused class', () => {
wrapper.find('input').simulate('focus');
wrapper.setProps({ reset: true });
assert(wrapper.find('.focused').length === 0);
cy.mount(<Test />).as('el');
cy.get('input').focus();
cy.get('.focused').should('exist');
cy.get<MountReturn>('@el').then(({ component, rerender }) => {
return rerender(cloneElement(component as ReactElement, { reset: true }));
});
cy.get('.focused').should('not.exist');
});
});
59 changes: 47 additions & 12 deletions components/mixin-ui-state/index.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,65 @@
import React, { Component } from 'react';
import React, {
Component,
HTMLAttributes,
DetailedReactHTMLElement,
ReactHTMLElement,
ReactSVGElement,
DOMElement,
DOMAttributes,
FunctionComponentElement,
CElement,
ComponentState,
ReactElement,
} from 'react';
import classnames from 'classnames';
import { func } from '../util';

const { makeChain } = func;
// UIState 为一些特殊元素的状态响应提供了标准的方式,
// 尤其适合CSS无法完全定制的控件,比如checkbox,radio等。
// 若组件 disable 则自行判断是否需要绑定状态管理。
// 注意:disable 不会触发事件,请使用resetUIState还原状态
/* eslint-disable react/prop-types */
class UIState extends Component {
constructor(props) {

type ClonableElement<P = unknown> =
| DetailedReactHTMLElement<HTMLAttributes<HTMLElement>, HTMLElement>
| ReactHTMLElement<HTMLElement>
| ReactSVGElement
| DOMElement<DOMAttributes<Element>, Element>
| FunctionComponentElement<P>
| CElement<P, Component<P, ComponentState>>
| ReactElement<P>;

export interface UIStateProps {
onFocus?: () => unknown;
onBlur?: () => unknown;
}

export interface UIStateState {
focused?: boolean;
}

/**
* UIState 为一些特殊元素的状态响应提供了标准的方式,
* 尤其适合 CSS 无法完全定制的控件,比如 checkbox,radio 等。
* 若组件 disable 则自行判断是否需要绑定状态管理。
* 注意:disable 不会触发事件,请使用 resetUIState 还原状态
*/
class UIState<
P extends UIStateProps = UIStateProps,
S extends UIStateState = UIStateState,
> extends Component<P, S> {
constructor(props: P & UIStateProps) {
super(props);
this.state = {};
['_onUIFocus', '_onUIBlur'].forEach(item => {
this.state = {} as S & UIStateState;
(['_onUIFocus', '_onUIBlur'] as const).forEach(item => {
this[item] = this[item].bind(this);
});
}
// base 事件绑定的元素
getStateElement(base) {
getStateElement(base: ClonableElement<P & UIStateProps>) {
const { onFocus, onBlur } = this.props;
return React.cloneElement(base, {
onFocus: makeChain(this._onUIFocus, onFocus),
onBlur: makeChain(this._onUIBlur, onBlur),
});
}
// 获取状态classname
// 获取状态 classname
getStateClassName() {
const { focused } = this.state;
return classnames({
Expand Down
1 change: 1 addition & 0 deletions components/mixin-ui-state/mobile/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// @ts-expect-error meet 未导出 MixinUiState
import { MixinUiState as MeetMixinUiState } from '@alifd/meet-react';
import NextMixinUiState from '../index';

Expand Down

0 comments on commit 86e5414

Please sign in to comment.