/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*
* Any modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
/*
* 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.
*/
jest.mock('lodash', () => ({
debounce: (fn: any) => fn,
}));
const nextTick = () => new Promise((res) => process.nextTick(res));
import {
EuiEmptyPrompt,
EuiListGroup,
EuiListGroupItem,
EuiLoadingSpinner,
EuiPagination,
EuiTablePagination,
} from '@elastic/eui';
import { IconType } from '@elastic/eui';
import { shallow } from 'enzyme';
import React from 'react';
import * as sinon from 'sinon';
import { SavedObjectFinderUi as SavedObjectFinder } from './saved_object_finder';
import { coreMock } from '../../../../core/public/mocks';
describe('SavedObjectsFinder', () => {
const doc = {
id: '1',
type: 'search',
attributes: { title: 'Example title' },
};
const doc2 = {
id: '2',
type: 'search',
attributes: { title: 'Another title' },
};
const doc3 = { type: 'vis', id: '3', attributes: { title: 'Vis' } };
const searchMetaData = [
{
type: 'search',
name: 'Search',
getIconForSavedObject: () => 'search' as IconType,
showSavedObject: () => true,
},
];
it('should call saved object client on startup', async () => {
const core = coreMock.createStart();
((core.savedObjects.client.find as any) as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ savedObjects: [doc] })
);
core.uiSettings.get.mockImplementation(() => 10);
const wrapper = shallow(
);
wrapper.instance().componentDidMount!();
expect(core.savedObjects.client.find).toHaveBeenCalledWith({
type: ['search'],
fields: ['title'],
search: undefined,
page: 1,
perPage: 10,
searchFields: ['title^3', 'description'],
defaultSearchOperator: 'AND',
});
});
it('should list initial items', async () => {
const core = coreMock.createStart();
((core.savedObjects.client.find as any) as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ savedObjects: [doc] })
);
core.uiSettings.get.mockImplementation(() => 10);
const wrapper = shallow(
);
wrapper.instance().componentDidMount!();
await nextTick();
expect(
wrapper.containsMatchingElement()
).toEqual(true);
});
it('should call onChoose on item click', async () => {
const chooseStub = sinon.stub();
const core = coreMock.createStart();
((core.savedObjects.client.find as any) as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ savedObjects: [doc] })
);
core.uiSettings.get.mockImplementation(() => 10);
const wrapper = shallow(
);
wrapper.instance().componentDidMount!();
await nextTick();
wrapper.find(EuiListGroupItem).first().simulate('click');
expect(chooseStub.calledWith('1', 'search', `${doc.attributes.title} (Search)`, doc)).toEqual(
true
);
});
describe('sorting', () => {
it('should list items ascending', async () => {
const core = coreMock.createStart();
((core.savedObjects.client.find as any) as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ savedObjects: [doc, doc2] })
);
core.uiSettings.get.mockImplementation(() => 10);
const wrapper = shallow(
);
wrapper.instance().componentDidMount!();
await nextTick();
const list = wrapper.find(EuiListGroup);
expect(list.childAt(0).key()).toBe('2');
expect(list.childAt(1).key()).toBe('1');
});
it('should list items descending', async () => {
const core = coreMock.createStart();
((core.savedObjects.client.find as any) as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ savedObjects: [doc, doc2] })
);
core.uiSettings.get.mockImplementation(() => 10);
const wrapper = shallow(
);
wrapper.instance().componentDidMount!();
await nextTick();
wrapper.setState({ sortDirection: 'desc' });
const list = wrapper.find(EuiListGroup);
expect(list.childAt(0).key()).toBe('1');
expect(list.childAt(1).key()).toBe('2');
});
});
it('should not show the saved objects which get filtered by showSavedObject', async () => {
const core = coreMock.createStart();
((core.savedObjects.client.find as any) as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ savedObjects: [doc, doc2] })
);
core.uiSettings.get.mockImplementation(() => 10);
const wrapper = shallow(
'search',
showSavedObject: ({ id }) => id !== '1',
},
]}
/>
);
wrapper.instance().componentDidMount!();
await nextTick();
const list = wrapper.find(EuiListGroup);
expect(list.childAt(0).key()).toBe('2');
expect(list.children().length).toBe(1);
});
describe('search', () => {
it('should request filtered list on search input', async () => {
const core = coreMock.createStart();
((core.savedObjects.client.find as any) as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ savedObjects: [doc, doc2] })
);
core.uiSettings.get.mockImplementation(() => 10);
const wrapper = shallow(
);
wrapper.instance().componentDidMount!();
await nextTick();
wrapper
.find('[data-test-subj="savedObjectFinderSearchInput"]')
.first()
.simulate('change', { target: { value: 'abc' } });
expect(core.savedObjects.client.find).toHaveBeenCalledWith({
type: ['search'],
fields: ['title'],
search: 'abc*',
page: 1,
perPage: 10,
searchFields: ['title^3', 'description'],
defaultSearchOperator: 'AND',
});
});
it('should include additional fields in search if listed in meta data', async () => {
const core = coreMock.createStart();
core.uiSettings.get.mockImplementation(() => 10);
(core.savedObjects.client.find as jest.Mock).mockResolvedValue({ savedObjects: [] });
const wrapper = shallow(
'search',
includeFields: ['field1', 'field2'],
},
{
type: 'type2',
name: '',
getIconForSavedObject: () => 'search',
includeFields: ['field2', 'field3'],
},
]}
/>
);
wrapper.instance().componentDidMount!();
await nextTick();
wrapper
.find('[data-test-subj="savedObjectFinderSearchInput"]')
.first()
.simulate('change', { target: { value: 'abc' } });
expect(core.savedObjects.client.find).toHaveBeenCalledWith({
type: ['type1', 'type2'],
fields: ['title', 'field1', 'field2', 'field3'],
search: 'abc*',
page: 1,
perPage: 10,
searchFields: ['title^3', 'description'],
defaultSearchOperator: 'AND',
});
});
it('should respect response order on search input', async () => {
const core = coreMock.createStart();
((core.savedObjects.client.find as any) as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ savedObjects: [doc, doc2] })
);
core.uiSettings.get.mockImplementation(() => 10);
const wrapper = shallow(
);
wrapper.instance().componentDidMount!();
await nextTick();
wrapper
.find('[data-test-subj="savedObjectFinderSearchInput"]')
.first()
.simulate('change', { target: { value: 'abc' } });
await nextTick();
const list = wrapper.find(EuiListGroup);
expect(list.childAt(0).key()).toBe('1');
expect(list.childAt(1).key()).toBe('2');
});
});
it('should request multiple saved object types at once', async () => {
const core = coreMock.createStart();
((core.savedObjects.client.find as any) as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ savedObjects: [doc, doc2] })
);
core.uiSettings.get.mockImplementation(() => 10);
const wrapper = shallow(
'search',
},
{
type: 'vis',
name: 'Vis',
getIconForSavedObject: () => 'visLine',
},
]}
/>
);
wrapper.instance().componentDidMount!();
expect(core.savedObjects.client.find).toHaveBeenCalledWith({
type: ['search', 'vis'],
fields: ['title'],
search: undefined,
page: 1,
perPage: 10,
searchFields: ['title^3', 'description'],
defaultSearchOperator: 'AND',
});
});
describe('filter', () => {
const metaDataConfig = [
{
type: 'search',
name: 'Search',
getIconForSavedObject: () => 'search' as IconType,
},
{
type: 'vis',
name: 'Vis',
getIconForSavedObject: () => 'document' as IconType,
},
];
it('should not render filter buttons if disabled', async () => {
const core = coreMock.createStart();
((core.savedObjects.client.find as any) as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({
savedObjects: [doc, doc2, doc3],
})
);
core.uiSettings.get.mockImplementation(() => 10);
const wrapper = shallow(
);
wrapper.instance().componentDidMount!();
await nextTick();
expect(wrapper.find('[data-test-subj="savedObjectFinderFilter-search"]').exists()).toBe(
false
);
});
it('should not render filter buttons if there is only one type in the list', async () => {
const core = coreMock.createStart();
((core.savedObjects.client.find as any) as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({
savedObjects: [doc, doc2],
})
);
core.uiSettings.get.mockImplementation(() => 10);
const wrapper = shallow(
);
wrapper.instance().componentDidMount!();
await nextTick();
expect(wrapper.find('[data-test-subj="savedObjectFinderFilter-search"]').exists()).toBe(
false
);
});
it('should apply filter if selected', async () => {
const core = coreMock.createStart();
((core.savedObjects.client.find as any) as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({
savedObjects: [doc, doc2, doc3],
})
);
core.uiSettings.get.mockImplementation(() => 10);
const wrapper = shallow(
);
wrapper.instance().componentDidMount!();
await nextTick();
wrapper.setState({ filteredTypes: ['vis'] });
const list = wrapper.find(EuiListGroup);
expect(list.childAt(0).key()).toBe('3');
expect(list.children().length).toBe(1);
wrapper.setState({ filteredTypes: ['vis', 'search'] });
expect(wrapper.find(EuiListGroup).children().length).toBe(3);
});
});
it('should display no items message if there are no items', async () => {
const core = coreMock.createStart();
((core.savedObjects.client.find as any) as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ savedObjects: [] })
);
core.uiSettings.get.mockImplementation(() => 10);
const noItemsMessage = ;
const wrapper = shallow(
);
wrapper.instance().componentDidMount!();
await nextTick();
expect(wrapper.find(EuiEmptyPrompt).first().prop('body')).toEqual(noItemsMessage);
});
describe('pagination', () => {
const longItemList = new Array(50).fill(undefined).map((_, i) => ({
id: String(i),
type: 'search',
attributes: {
title: `Title ${i < 10 ? '0' : ''}${i}`,
},
}));
it('should show a table pagination with initial per page', async () => {
const core = coreMock.createStart();
((core.savedObjects.client.find as any) as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ savedObjects: longItemList })
);
core.uiSettings.get.mockImplementation(() => 10);
const wrapper = shallow(
);
wrapper.instance().componentDidMount!();
await nextTick();
expect(wrapper.find(EuiTablePagination).first().prop('itemsPerPage')).toEqual(15);
expect(wrapper.find(EuiListGroup).children().length).toBe(15);
});
it('should allow switching the page size', async () => {
const core = coreMock.createStart();
((core.savedObjects.client.find as any) as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ savedObjects: longItemList })
);
core.uiSettings.get.mockImplementation(() => 10);
const wrapper = shallow(
);
wrapper.instance().componentDidMount!();
await nextTick();
wrapper.find(EuiTablePagination).first().prop('onChangeItemsPerPage')!(5);
expect(wrapper.find(EuiListGroup).children().length).toBe(5);
});
it('should switch page correctly', async () => {
const core = coreMock.createStart();
((core.savedObjects.client.find as any) as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ savedObjects: longItemList })
);
core.uiSettings.get.mockImplementation(() => 10);
const wrapper = shallow(
);
wrapper.instance().componentDidMount!();
await nextTick();
wrapper.find(EuiTablePagination).first().prop('onChangePage')!(1);
expect(wrapper.find(EuiListGroup).children().first().key()).toBe('15');
});
it('should show an ordinary pagination for fixed page sizes', async () => {
const core = coreMock.createStart();
((core.savedObjects.client.find as any) as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ savedObjects: longItemList })
);
core.uiSettings.get.mockImplementation(() => 10);
const wrapper = shallow(
);
wrapper.instance().componentDidMount!();
await nextTick();
expect(wrapper.find(EuiPagination).first().prop('pageCount')).toEqual(2);
expect(wrapper.find(EuiListGroup).children().length).toBe(33);
});
it('should switch page correctly for fixed page sizes', async () => {
const core = coreMock.createStart();
((core.savedObjects.client.find as any) as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ savedObjects: longItemList })
);
core.uiSettings.get.mockImplementation(() => 10);
const wrapper = shallow(
);
wrapper.instance().componentDidMount!();
await nextTick();
wrapper.find(EuiPagination).first().prop('onPageClick')!(1);
expect(wrapper.find(EuiListGroup).children().first().key()).toBe('33');
});
});
describe('loading state', () => {
it('should display a spinner during initial loading', () => {
const core = coreMock.createStart();
(core.savedObjects.client.find as jest.Mock).mockResolvedValue({ savedObjects: [] });
const wrapper = shallow(
);
expect(wrapper.containsMatchingElement()).toBe(true);
});
it('should hide the spinner if data is shown', async () => {
const core = coreMock.createStart();
((core.savedObjects.client.find as any) as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ savedObjects: [doc] })
);
const wrapper = shallow(
'search',
},
]}
/>
);
wrapper.instance().componentDidMount!();
await nextTick();
expect(wrapper.containsMatchingElement()).toBe(false);
});
it('should not show the spinner if there are already items', async () => {
const core = coreMock.createStart();
((core.savedObjects.client.find as any) as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ savedObjects: [doc] })
);
const wrapper = shallow(
);
wrapper.instance().componentDidMount!();
await nextTick();
wrapper
.find('[data-test-subj="savedObjectFinderSearchInput"]')
.first()
.simulate('change', { target: { value: 'abc' } });
wrapper.update();
expect(wrapper.containsMatchingElement()).toBe(false);
});
});
it('should render with children', async () => {
const core = coreMock.createStart();
((core.savedObjects.client.find as any) as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ savedObjects: [doc, doc2] })
);
core.uiSettings.get.mockImplementation(() => 10);
const wrapper = shallow(
'search',
},
{
type: 'vis',
name: 'Vis',
getIconForSavedObject: () => 'visLine',
},
]}
>
);
expect(wrapper.exists('#testChildButton')).toBe(true);
});
});