mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[test_utils/Testbed] Move to src/test_utils folder (OSS) (#66898)
This commit is contained in:
parent
b291fa8932
commit
03b6c6d638
13 changed files with 51 additions and 259 deletions
|
@ -1,208 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Components using the react-intl module require access to the intl context.
|
||||
* This is not available when mounting single components in Enzyme.
|
||||
* These helper functions aim to address that and wrap a valid,
|
||||
* intl context around them.
|
||||
*/
|
||||
|
||||
import { I18nProvider, InjectedIntl, intlShape } from '@kbn/i18n/react';
|
||||
import { mount, ReactWrapper, render, shallow } from 'enzyme';
|
||||
import React, { ReactElement, ValidationMap } from 'react';
|
||||
import { act as reactAct } from 'react-dom/test-utils';
|
||||
|
||||
// Use fake component to extract `intl` property to use in tests.
|
||||
const { intl } = (mount(
|
||||
<I18nProvider>
|
||||
<br />
|
||||
</I18nProvider>
|
||||
).find('IntlProvider') as ReactWrapper<{}, {}, import('react-intl').IntlProvider>)
|
||||
.instance()
|
||||
.getChildContext();
|
||||
|
||||
function getOptions(context = {}, childContextTypes = {}, props = {}) {
|
||||
return {
|
||||
context: {
|
||||
...context,
|
||||
intl,
|
||||
},
|
||||
childContextTypes: {
|
||||
...childContextTypes,
|
||||
intl: intlShape,
|
||||
},
|
||||
...props,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* When using React-Intl `injectIntl` on components, props.intl is required.
|
||||
*/
|
||||
function nodeWithIntlProp<T>(node: ReactElement<T>): ReactElement<T & { intl: InjectedIntl }> {
|
||||
return React.cloneElement<any>(node, { intl });
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the wrapper instance using shallow with provided intl object into context
|
||||
*
|
||||
* @param node The React element or cheerio wrapper
|
||||
* @param options properties to pass into shallow wrapper
|
||||
* @return The wrapper instance around the rendered output with intl object in context
|
||||
*/
|
||||
export function shallowWithIntl<T>(
|
||||
node: ReactElement<T>,
|
||||
{
|
||||
context,
|
||||
childContextTypes,
|
||||
...props
|
||||
}: {
|
||||
context?: any;
|
||||
childContextTypes?: ValidationMap<any>;
|
||||
} = {}
|
||||
) {
|
||||
const options = getOptions(context, childContextTypes, props);
|
||||
|
||||
return shallow(nodeWithIntlProp(node), options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the wrapper instance using mount with provided intl object into context
|
||||
*
|
||||
* @param node The React element or cheerio wrapper
|
||||
* @param options properties to pass into mount wrapper
|
||||
* @return The wrapper instance around the rendered output with intl object in context
|
||||
*/
|
||||
export function mountWithIntl<T>(
|
||||
node: ReactElement<T>,
|
||||
{
|
||||
context,
|
||||
childContextTypes,
|
||||
...props
|
||||
}: {
|
||||
context?: any;
|
||||
childContextTypes?: ValidationMap<any>;
|
||||
} = {}
|
||||
) {
|
||||
const options = getOptions(context, childContextTypes, props);
|
||||
|
||||
return mount(nodeWithIntlProp(node), options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the wrapper instance using render with provided intl object into context
|
||||
*
|
||||
* @param node The React element or cheerio wrapper
|
||||
* @param options properties to pass into render wrapper
|
||||
* @return The wrapper instance around the rendered output with intl object in context
|
||||
*/
|
||||
export function renderWithIntl<T>(
|
||||
node: ReactElement<T>,
|
||||
{
|
||||
context,
|
||||
childContextTypes,
|
||||
...props
|
||||
}: {
|
||||
context?: any;
|
||||
childContextTypes?: ValidationMap<any>;
|
||||
} = {}
|
||||
) {
|
||||
const options = getOptions(context, childContextTypes, props);
|
||||
|
||||
return render(nodeWithIntlProp(node), options);
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper object to provide access to the state of a hook under test and to
|
||||
* enable interaction with that hook.
|
||||
*/
|
||||
interface ReactHookWrapper<Args, HookValue> {
|
||||
/* Ensures that async React operations have settled before and after the
|
||||
* given actor callback is called. The actor callback arguments provide easy
|
||||
* access to the last hook value and allow for updating the arguments passed
|
||||
* to the hook body to trigger reevaluation.
|
||||
*/
|
||||
act: (actor: (lastHookValue: HookValue, setArgs: (args: Args) => void) => void) => void;
|
||||
/* The enzyme wrapper around the test component. */
|
||||
component: ReactWrapper;
|
||||
/* The most recent value return the by test harness of the hook. */
|
||||
getLastHookValue: () => HookValue;
|
||||
/* The jest Mock function that receives the hook values for introspection. */
|
||||
hookValueCallback: jest.Mock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows for execution of hooks inside of a test component which records the
|
||||
* returned values.
|
||||
*
|
||||
* @param body A function that calls the hook and returns data derived from it
|
||||
* @param WrapperComponent A component that, if provided, will be wrapped
|
||||
* around the test component. This can be useful to provide context values.
|
||||
* @return {ReactHookWrapper} An object providing access to the hook state and
|
||||
* functions to interact with it.
|
||||
*/
|
||||
export const mountHook = <Args extends {}, HookValue extends any>(
|
||||
body: (args: Args) => HookValue,
|
||||
WrapperComponent?: React.ComponentType,
|
||||
initialArgs: Args = {} as Args
|
||||
): ReactHookWrapper<Args, HookValue> => {
|
||||
const hookValueCallback = jest.fn();
|
||||
let component!: ReactWrapper;
|
||||
|
||||
const act: ReactHookWrapper<Args, HookValue>['act'] = (actor) => {
|
||||
reactAct(() => {
|
||||
actor(getLastHookValue(), (args: Args) => component.setProps(args));
|
||||
component.update();
|
||||
});
|
||||
};
|
||||
|
||||
const getLastHookValue = () => {
|
||||
const calls = hookValueCallback.mock.calls;
|
||||
if (calls.length <= 0) {
|
||||
throw Error('No recent hook value present.');
|
||||
}
|
||||
return calls[calls.length - 1][0];
|
||||
};
|
||||
|
||||
const HookComponent = (props: Args) => {
|
||||
hookValueCallback(body(props));
|
||||
return null;
|
||||
};
|
||||
const TestComponent: React.FunctionComponent<Args> = (args) =>
|
||||
WrapperComponent ? (
|
||||
<WrapperComponent>
|
||||
<HookComponent {...args} />
|
||||
</WrapperComponent>
|
||||
) : (
|
||||
<HookComponent {...args} />
|
||||
);
|
||||
|
||||
reactAct(() => {
|
||||
component = mount(<TestComponent {...initialArgs} />);
|
||||
});
|
||||
|
||||
return {
|
||||
act,
|
||||
component,
|
||||
getLastHookValue,
|
||||
hookValueCallback,
|
||||
};
|
||||
};
|
||||
|
||||
export const nextTick = () => new Promise((res) => process.nextTick(res));
|
|
@ -1,21 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export * from './testbed';
|
||||
export * from './lib';
|
|
@ -1,20 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export { nextTick, getRandomString, getRandomNumber } from './utils';
|
|
@ -17,8 +17,6 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export {
|
||||
registerTestBed,
|
||||
getRandomString,
|
||||
TestBed,
|
||||
} from '../../../__packages_do_not_import__/test_utils_temp';
|
||||
export { registerTestBed, TestBed } from '../../../../../test_utils/public/testbed';
|
||||
|
||||
export { getRandomString } from '../../../../../test_utils/public/helpers';
|
||||
|
|
|
@ -17,10 +17,10 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export { mountWithIntl } from './enzyme_helpers';
|
||||
|
||||
export { findTestSubject } from './find_test_subject';
|
||||
|
||||
export { WithStore } from './redux_helpers';
|
||||
|
||||
export { WithMemoryRouter, WithRoute, reactRouterMock } from './router_helpers';
|
||||
|
||||
export * from './utils';
|
|
@ -22,7 +22,8 @@ import { Store } from 'redux';
|
|||
import { ReactWrapper } from 'enzyme';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
|
||||
import { mountWithIntl, WithMemoryRouter, WithRoute, WithStore } from '../helpers';
|
||||
import { mountWithIntl } from '../enzyme_helpers';
|
||||
import { WithMemoryRouter, WithRoute, WithStore } from '../helpers';
|
||||
import { MemoryRouterConfig } from './types';
|
||||
|
||||
interface Config {
|
||||
|
@ -66,7 +67,7 @@ export const mountComponentAsync = async (config: Config): Promise<ReactWrapper>
|
|||
});
|
||||
|
||||
// @ts-ignore
|
||||
return component;
|
||||
return component.update();
|
||||
};
|
||||
|
||||
export const getJSXComponentWithProps = (Component: ComponentType, props: any) => (
|
|
@ -214,6 +214,24 @@ export const registerTestBed = <T extends string = string>(
|
|||
return new Promise((resolve) => setTimeout(resolve));
|
||||
};
|
||||
|
||||
const setSelectValue: TestBed<T>['form']['setSelectValue'] = (
|
||||
select,
|
||||
value,
|
||||
doUpdateComponent = true
|
||||
) => {
|
||||
const formSelect = typeof select === 'string' ? find(select) : (select as ReactWrapper);
|
||||
|
||||
if (!formSelect.length) {
|
||||
throw new Error(`Select "${select}" was not found.`);
|
||||
}
|
||||
|
||||
formSelect.simulate('change', { target: { value } });
|
||||
|
||||
if (doUpdateComponent) {
|
||||
component.update();
|
||||
}
|
||||
};
|
||||
|
||||
const selectCheckBox: TestBed<T>['form']['selectCheckBox'] = (
|
||||
testSubject,
|
||||
isChecked = true
|
||||
|
@ -311,6 +329,7 @@ export const registerTestBed = <T extends string = string>(
|
|||
},
|
||||
form: {
|
||||
setInputValue,
|
||||
setSelectValue,
|
||||
selectCheckBox,
|
||||
toggleEuiSwitch,
|
||||
setComboBoxValue,
|
|
@ -54,7 +54,7 @@ export interface TestBed<T = string> {
|
|||
*
|
||||
* @example
|
||||
*
|
||||
```ts
|
||||
```typescript
|
||||
find('nameInput');
|
||||
// or more specific,
|
||||
// "nameInput" is a child of "myForm"
|
||||
|
@ -92,6 +92,29 @@ export interface TestBed<T = string> {
|
|||
value: string,
|
||||
isAsync?: boolean
|
||||
) => Promise<void> | void;
|
||||
/**
|
||||
* Set the value of a <EuiSelect /> or a mocked <EuiSuperSelect />
|
||||
* For the <EuiSuperSelect /> you need to mock it like this
|
||||
*
|
||||
```typescript
|
||||
jest.mock('@elastic/eui', () => ({
|
||||
...jest.requireActual('@elastic/eui'),
|
||||
EuiSuperSelect: (props: any) => (
|
||||
<input
|
||||
data-test-subj={props['data-test-subj'] || 'mockSuperSelect'}
|
||||
value={props.valueOfSelected}
|
||||
onChange={e => {
|
||||
props.onChange(e.target.value);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
}));
|
||||
```
|
||||
* @param select The form select. Can either be a data-test-subj or a reactWrapper (can be a nested path. e.g. "myForm.myInput").
|
||||
* @param value The value to set
|
||||
* @param doUpdateComponent Call component.update() after changing the select value
|
||||
*/
|
||||
setSelectValue: (select: T | ReactWrapper, value: string, doUpdateComponent?: boolean) => void;
|
||||
/**
|
||||
* Select or unselect a form checkbox.
|
||||
*
|
Loading…
Add table
Add a link
Reference in a new issue