Doug Lauder
2023-03-22 17:22:27 -04:00
коммит произвёл GitHub
родитель b61c096497
Коммит c943ed6859
13276 изменённых файлов: 1695615 добавлений и 223189 удалений

Просмотреть файл

@@ -0,0 +1,31 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`components/ModalController component should match snapshot without any modals 1`] = `
<Provider
store={
Object {
"clearActions": [Function],
"dispatch": [Function],
"getActions": [Function],
"getState": [Function],
"replaceReducer": [Function],
"subscribe": [Function],
}
}
>
<Connect(ModalController)>
<ModalController
actions={
Object {
"closeModal": [Function],
}
}
modals={
Object {
"modalState": Object {},
}
}
/>
</Connect(ModalController)>
</Provider>
`;

Просмотреть файл

@@ -0,0 +1,33 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {connect} from 'react-redux';
import {ActionCreatorsMapObject, bindActionCreators, Dispatch} from 'redux';
import {GlobalState} from 'types/store/index.js';
import {Action, GenericAction} from 'mattermost-redux/types/actions.js';
import {closeModal} from 'actions/views/modals';
import ModalController from './modal_controller';
function mapStateToProps(state: GlobalState) {
return {
modals: state.views.modals,
};
}
type Actions = {
closeModal: (modalId: string) => void;
}
function mapDispatchToProps(dispatch: Dispatch<GenericAction>) {
return {
actions: bindActionCreators<ActionCreatorsMapObject<Action>, Actions>({
closeModal,
}, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(ModalController);

Просмотреть файл

@@ -0,0 +1,172 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {Modal} from 'react-bootstrap';
import {Provider} from 'react-redux';
import {mount} from 'enzyme';
import {closeModal} from 'actions/views/modals';
import mockStore from 'tests/test_store';
import ModalController from '.';
type TestModalProps = {
onExited: () => void;
}
type TestModalState = {
show: boolean;
}
class TestModal extends React.PureComponent<TestModalProps, TestModalState> {
constructor(props: TestModalProps) {
super(props);
this.state = {
show: true,
};
}
hideModal = () => {
this.setState({show: false});
}
render() {
return (
<Modal
show={this.state.show}
onHide={this.hideModal}
onExited={this.props.onExited}
>
<Modal.Header closeButton={true}/>
<Modal.Body/>
</Modal>
);
}
}
describe('components/ModalController', () => {
const modalId = 'test_modal';
test('component should match snapshot without any modals', () => {
const state = {
views: {
modals: {
modalState: {},
},
},
};
const store = mockStore(state);
const wrapper = mount(
<Provider store={store}>
<ModalController/>
</Provider>,
);
expect(wrapper).toMatchSnapshot();
expect(wrapper.find('ModalController > *').length).toBe(0);
expect(document.getElementsByClassName('modal-dialog').length).toBeFalsy();
});
test('test model should be open', () => {
const state = {
views: {
modals: {
modalState: {
[modalId]: {
open: true,
dialogProps: {},
dialogType: TestModal,
},
},
},
},
};
const store = mockStore(state);
mount(
<Provider store={store}>
<ModalController/>
</Provider>,
);
expect(document.getElementsByClassName('modal-dialog').length).toBe(1);
});
test('should pass onExited to modal to allow a modal to remove itself', () => {
const state = {
views: {
modals: {
modalState: {
[modalId]: {
open: true,
dialogProps: {},
dialogType: TestModal,
},
},
},
},
};
const store = mockStore(state);
const wrapper = mount(
<Provider store={store}>
<ModalController/>
</Provider>,
);
expect(wrapper.find(TestModal).exists()).toBe(true);
expect(wrapper.find(TestModal).prop('onExited')).toBeDefined();
expect(wrapper.find(Modal).prop('onExited')).toBeDefined();
wrapper.find(TestModal).prop('onExited')!();
expect(store.getActions()).toEqual([
closeModal(modalId),
]);
});
test('should call a provided onExited in addition to removing the modal', () => {
const onExited = jest.fn();
const state = {
views: {
modals: {
modalState: {
[modalId]: {
open: true,
dialogProps: {
onExited,
},
dialogType: TestModal,
},
},
},
},
};
const store = mockStore(state);
const wrapper = mount(
<Provider store={store}>
<ModalController/>
</Provider>,
);
expect(wrapper.find(TestModal).exists()).toBe(true);
expect(wrapper.find(TestModal).prop('onExited')).toBeDefined();
expect(wrapper.find(Modal).prop('onExited')).toBeDefined();
expect(onExited).not.toBeCalled();
wrapper.find(TestModal).prop('onExited')!();
expect(onExited).toBeCalled();
});
});

Просмотреть файл

@@ -0,0 +1,70 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
type Modal = {
open: boolean;
dialogProps: Record<string, any>;
dialogType: React.ComponentType;
}
type Props = {
/*
* Object that has map of modal's id and element
*/
modals: {
modalState: {
[modalId: string]: Modal;
};
};
/*
* Object with action creators
*/
actions: {
/*
* Action creator to close modal
*/
closeModal: (modalId: string) => void;
};
}
export default class ModalController extends React.PureComponent<Props> {
public render(): React.ReactNode {
const {modals, ...props} = this.props;
const {modalState} = modals;
if (!modals) {
return null;
}
const modalOutput = [];
for (const modalId in modalState) {
if (modalState.hasOwnProperty(modalId)) {
const modal = modalState[modalId];
if (modal.open) {
const modalComponent = React.createElement(modal.dialogType, Object.assign({}, modal.dialogProps, {
onExited: () => {
props.actions.closeModal(modalId);
// Call any onExited prop provided by whoever opened the modal, if one was provided
modal.dialogProps?.onExited?.();
},
onHide: props.actions.closeModal.bind(this, modalId),
key: `${modalId}_modal`,
}));
modalOutput.push(modalComponent);
}
}
}
return (
<>{modalOutput}</>
);
}
}