Skip to content

Commit 30bf355

Browse files
committed
Add unit tests and minor code improvements
1 parent bcf128d commit 30bf355

File tree

8 files changed

+139
-38
lines changed

8 files changed

+139
-38
lines changed

x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77

88
import React, { useCallback, useMemo, useEffect } from 'react';
99
import { connect, ConnectedProps, useDispatch } from 'react-redux';
10-
import styled from 'styled-components';
1110
import deepEqual from 'fast-deep-equal';
11+
import styled from 'styled-components';
1212
import { isEmpty } from 'lodash/fp';
1313
import { inputsModel, inputsSelectors, State } from '../../store';
1414
import { inputsActions } from '../../store/actions';
@@ -31,7 +31,7 @@ import { defaultControlColumn } from '../../../timelines/components/timeline/bod
3131
import { EventsViewer } from './events_viewer';
3232
import * as i18n from './translations';
3333
import { GraphOverlay } from '../../../timelines/components/graph_overlay';
34-
import { CreateFieldButton } from '../../../timelines/components/create_runtime_field';
34+
import { CreateFieldButton } from '../../../timelines/components/create_field';
3535
import { CreateFieldComponentType } from '../../../../../timelines/public';
3636

3737
const EMPTY_CONTROL_COLUMNS: ControlColumnProps[] = [];
@@ -176,7 +176,7 @@ const StatefulEventsViewerComponent: React.FC<Props> = ({
176176
const bulkActions = useMemo(() => ({ onAlertStatusActionSuccess }), [onAlertStatusActionSuccess]);
177177

178178
const createFieldComponent = useMemo(() => {
179-
// It has to receive onClick props from TGrid in order to close browserFields modal.
179+
// It receives onClick props from field browser in order to close the modal.
180180
const CreateFieldButtonComponent: CreateFieldComponentType = ({ onClick }) => (
181181
<CreateFieldButton
182182
selectedDataViewId={selectedDataViewId}

x-pack/plugins/security_solution/public/common/containers/source/index.test.tsx

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ jest.mock('react-redux', () => {
3030
useDispatch: () => mockDispatch,
3131
};
3232
});
33-
jest.mock('../../lib/kibana'); // , () => ({
33+
jest.mock('../../lib/kibana');
3434

3535
describe('source/index.tsx', () => {
3636
describe('getBrowserFields', () => {
@@ -39,11 +39,11 @@ describe('source/index.tsx', () => {
3939
expect(fields).toEqual({});
4040
});
4141

42-
test('it returns the same input with the same title', () => {
43-
getBrowserFields('title 1', []);
44-
// Since it is memoized it will return the same output which is empty object given 'title 1' a second time
45-
const fields = getBrowserFields('title 1', mocksSource.indexFields as IndexField[]);
46-
expect(fields).toEqual({});
42+
test('it returns the same input given the same title and same fields length', () => {
43+
const oldFields = getBrowserFields('title 1', mocksSource.indexFields as IndexField[]);
44+
const newFields = getBrowserFields('title 1', mocksSource.indexFields as IndexField[]);
45+
// Since it is memoized it will return the same object instance
46+
expect(newFields).toBe(oldFields);
4747
});
4848

4949
test('it transforms input into output as expected', () => {
@@ -282,5 +282,44 @@ describe('source/index.tsx', () => {
282282
);
283283
});
284284
});
285+
286+
it('doesnt set source when `autoCall` is false', async () => {
287+
await act(async () => {
288+
const { rerender, waitForNextUpdate } = renderHook<string, void>(
289+
() => useIndexFields(SourcererScopeName.default, false),
290+
{
291+
wrapper: ({ children }) => <Provider store={store}>{children}</Provider>,
292+
}
293+
);
294+
await waitForNextUpdate();
295+
rerender();
296+
expect(mockDispatch).not.toBeCalled();
297+
});
298+
});
299+
300+
it('sets source when `autoCall` is false and when indexFieldsSearch is called', async () => {
301+
await act(async () => {
302+
const { rerender, waitForNextUpdate, result } = renderHook<
303+
string,
304+
ReturnType<typeof useIndexFields>
305+
>(() => useIndexFields(SourcererScopeName.default, false), {
306+
wrapper: ({ children }) => <Provider store={store}>{children}</Provider>,
307+
});
308+
await waitForNextUpdate();
309+
rerender();
310+
311+
result.current.indexFieldsSearch(sourcererState.defaultDataView.id);
312+
313+
expect(mockDispatch).toHaveBeenCalledTimes(2);
314+
expect(mockDispatch.mock.calls[0][0]).toHaveProperty(
315+
'type',
316+
'x-pack/security_solution/local/sourcerer/SET_SOURCERER_SCOPE_LOADING'
317+
);
318+
expect(mockDispatch.mock.calls[1][0]).toHaveProperty(
319+
'type',
320+
'x-pack/security_solution/local/sourcerer/SET_SOURCE'
321+
);
322+
});
323+
});
285324
});
286325
});
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import { render, fireEvent, act, screen } from '@testing-library/react';
9+
import React from 'react';
10+
import { SourcererScopeName } from '../../../common/store/sourcerer/model';
11+
import { CreateFieldButton } from './index';
12+
import {
13+
indexPatternFieldEditorPluginMock,
14+
Start,
15+
} from '../../../../../../../src/plugins/index_pattern_field_editor/public/mocks';
16+
17+
import { TestProviders } from '../../../common/mock';
18+
import { useKibana } from '../../../common/lib/kibana';
19+
import { DataView } from '../../../../../../../src/plugins/data/common';
20+
21+
const useKibanaMock = useKibana as jest.Mocked<typeof useKibana>;
22+
23+
let mockIndexPatternFieldEditor: Start;
24+
jest.mock('../../../common/lib/kibana');
25+
26+
jest.mock('../../../common/containers/source', () => ({ useIndexFields: () => ({}) }));
27+
28+
const runAllPromises = () => new Promise(setImmediate);
29+
30+
describe('CreateFieldButton', () => {
31+
beforeEach(() => {
32+
mockIndexPatternFieldEditor = indexPatternFieldEditorPluginMock.createStartContract();
33+
useKibanaMock().services.indexPatternFieldEditor = mockIndexPatternFieldEditor;
34+
useKibanaMock().services.data.dataViews.get = () => new Promise(() => undefined);
35+
});
36+
37+
it('displays the button when user has permissions', () => {
38+
mockIndexPatternFieldEditor.userPermissions.editIndexPattern = () => true;
39+
40+
render(
41+
<CreateFieldButton
42+
selectedDataViewId={'dataViewId'}
43+
onClick={() => undefined}
44+
scopeId={SourcererScopeName.timeline}
45+
/>,
46+
{ wrapper: TestProviders }
47+
);
48+
49+
expect(screen.getByRole('button')).toBeInTheDocument();
50+
});
51+
52+
it("doesn't display the button when user doesn't have permissions", () => {
53+
mockIndexPatternFieldEditor.userPermissions.editIndexPattern = () => false;
54+
render(
55+
<CreateFieldButton
56+
selectedDataViewId={'dataViewId'}
57+
onClick={() => undefined}
58+
scopeId={SourcererScopeName.timeline}
59+
/>,
60+
{ wrapper: TestProviders }
61+
);
62+
63+
expect(screen.queryByRole('button')).not.toBeInTheDocument();
64+
});
65+
66+
it("calls 'onClick' param when the button is clicked", async () => {
67+
mockIndexPatternFieldEditor.userPermissions.editIndexPattern = () => true;
68+
useKibanaMock().services.data.dataViews.get = () => Promise.resolve({} as DataView);
69+
70+
const onClickParam = jest.fn();
71+
await act(async () => {
72+
render(
73+
<CreateFieldButton
74+
selectedDataViewId={'dataViewId'}
75+
onClick={onClickParam}
76+
scopeId={SourcererScopeName.timeline}
77+
/>,
78+
{ wrapper: TestProviders }
79+
);
80+
await runAllPromises();
81+
});
82+
83+
fireEvent.click(screen.getByRole('button'));
84+
expect(onClickParam).toHaveBeenCalled();
85+
});
86+
});
Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
*/
77

88
import React, { useCallback, useEffect, useState } from 'react';
9-
import { EuiButton, EuiLoadingSpinner } from '@elastic/eui';
9+
import { EuiButton } from '@elastic/eui';
1010
import styled from 'styled-components';
1111

1212
import { IndexPattern } from '../../../../../../../src/plugins/data/public';
@@ -46,7 +46,6 @@ export const CreateFieldButton = React.memo<CreateFieldButtonProps>(
4646
if (dataView) {
4747
indexPatternFieldEditor?.openEditor({
4848
ctx: { indexPattern: dataView },
49-
// fieldName: item.fieldName, edit?
5049
onSave: () => {
5150
indexFieldsSearch(selectedDataViewId);
5251
},
@@ -76,22 +75,3 @@ export const CreateFieldButton = React.memo<CreateFieldButtonProps>(
7675
);
7776

7877
CreateFieldButton.displayName = 'CreateFieldButton';
79-
80-
// MUST HAVE
81-
// [x] alerts - Fix scope id bug. When I call useIndexFields it fetch the data and set t-grid loading state...
82-
// [x] Close modal
83-
// [x] refetch data after creating field
84-
// [x] check user permissions
85-
// [x] timeline
86-
// [x] display flyout over timeline (z-index issue)
87-
// [x] Fix create field loading state
88-
89-
// TODO
90-
// Add cypress test
91-
// Fix unit tests
92-
93-
// NICE TO HAVE
94-
// Change "create field" flyout subtitle
95-
// edit field
96-
// delete field
97-
// automaticaly display the added field? (it shows on one mockup)

x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ import { Events } from './events';
4545
import { DEFAULT_ICON_BUTTON_WIDTH } from '../helpers';
4646
import { useDeepEqualSelector } from '../../../../common/hooks/use_selector';
4747
import { SourcererScopeName } from '../../../../common/store/sourcerer/model';
48-
import { CreateFieldButton } from '../../create_runtime_field';
48+
import { CreateFieldButton } from '../../create_field_button';
4949
import { SelectedDataView } from '../../../../common/store/sourcerer/selectors';
5050

5151
interface OwnProps {
@@ -234,11 +234,11 @@ export const BodyComponent = React.memo<StatefulBodyProps>(
234234
);
235235

236236
const createFieldComponent = useMemo(() => {
237-
// It has to receive onClick props from TGrid in order to close browserFields modal.
237+
// It receives onClick props from field browser in order to close the modal.
238238
const CreateFieldButtonComponent: CreateFieldComponentType = ({ onClick }) => (
239239
<CreateFieldButton
240240
selectedDataViewId={dataViewId}
241-
onClick={onClick} // close browserField and make sure create field modal is on top of timeline modal
241+
onClick={onClick}
242242
scopeId={SourcererScopeName.timeline}
243243
/>
244244
);

x-pack/plugins/timelines/public/components/t_grid/toolbar/fields_browser/field_browser.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ const FieldsBrowserComponent: React.FC<Props> = ({
208208
/>
209209
</EuiFlexItem>
210210
<EuiFlexItem grow={false}>
211-
{CreateField && <CreateField onClick={closeAndRestoreFocus} />}
211+
{CreateField && <CreateField onClick={onHide} />}
212212
</EuiFlexItem>
213213
</EuiFlexGroup>
214214

x-pack/yarn.lock

Lines changed: 0 additions & 4 deletions
This file was deleted.

0 commit comments

Comments
 (0)