Skip to content

Commit

Permalink
[Visualize] Add unit tests (#70410)
Browse files Browse the repository at this point in the history
* Reactify visualize app

* Fix typescript failures after merging master

* Make sure refresh button works

* Subscribe filter manager fetches

* Use redirect to landing page

* Update savedSearch type

* Add check for TSVB is loaded

* Add unit tests for useSavedVisInstance effect

* Fix comments

* Fix uiState persistence on vis load

* Remove extra div around TableListView

* Update DTS selectors

* Add error handling for embeddable

* Add unit tests for createVisualizeAppState

* Add unit tests for useChromeVisibility

* Add filter_manager.mock

* Add unit tests for useVisualizeAppState

* Use app state stub

* Add unit tests for useLinkedSearchUpdates

* Add unit tests for useEditorUpdates

* Remove extra argument from useEditorUpdates effect

* Update comments, fix typos

* Remove extra div wrapper

* Apply design suggestions

* Revert accidental config changes

* Add unit tests for useEditorUpdates

* Use visualize services mock

* Add unit tests for getVisualizationInstance

* Fix eslint warnings

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
  • Loading branch information
sulemanof and elasticmachine authored Jul 8, 2020
1 parent 0ebddcf commit 1c91b1c
Show file tree
Hide file tree
Showing 13 changed files with 1,331 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* 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.
*/

import { Observable } from 'rxjs';
import { FilterManager } from './filter_manager';

export const createFilterManagerMock = () => {
const filterManager = ({
mergeIncomingFilters: jest.fn(),
handleStateUpdate: jest.fn(),
getFilters: jest.fn(),
getAppFilters: jest.fn(),
getGlobalFilters: jest.fn(),
getPartitionedFilters: jest.fn(),
getUpdates$: jest.fn(() => new Observable()),
getFetches$: jest.fn(() => new Observable()),
addFilters: jest.fn(),
setFilters: jest.fn(),
setGlobalFilters: jest.fn(),
setAppFilters: jest.fn(),
removeFilter: jest.fn(),
removeAll: jest.fn(),
} as unknown) as jest.Mocked<FilterManager>;

return filterManager;
};
5 changes: 3 additions & 2 deletions src/plugins/data/public/query/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,13 @@
import { Observable } from 'rxjs';
import { QueryService, QuerySetup, QueryStart } from '.';
import { timefilterServiceMock } from './timefilter/timefilter_service.mock';
import { createFilterManagerMock } from './filter_manager/filter_manager.mock';

type QueryServiceClientContract = PublicMethodsOf<QueryService>;

const createSetupContractMock = () => {
const setupContract: jest.Mocked<QuerySetup> = {
filterManager: jest.fn() as any,
filterManager: createFilterManagerMock(),
timefilter: timefilterServiceMock.createSetupContract(),
state$: new Observable(),
};
Expand All @@ -36,7 +37,7 @@ const createSetupContractMock = () => {
const createStartContractMock = () => {
const startContract: jest.Mocked<QueryStart> = {
addToQueryLog: jest.fn(),
filterManager: jest.fn() as any,
filterManager: createFilterManagerMock(),
savedQueries: jest.fn() as any,
state$: new Observable(),
timefilter: timefilterServiceMock.createStartContract(),
Expand Down
4 changes: 3 additions & 1 deletion src/plugins/visualizations/public/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ const createStartContract = (): VisualizationsStart => ({
get: jest.fn(),
all: jest.fn(),
getAliases: jest.fn(),
savedVisualizationsLoader: {} as any,
savedVisualizationsLoader: {
get: jest.fn(),
} as any,
showNewVisModal: jest.fn(),
createVis: jest.fn(),
convertFromSerializedVis: jest.fn(),
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/visualize/public/application/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export type PureVisState = SavedVisState;

export interface VisualizeAppState {
filters: Filter[];
uiState: PersistedState;
uiState: Record<string, unknown>;
vis: PureVisState;
query: Query;
savedQuery?: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/*
* 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.
*/

import { IKbnUrlStateStorage } from 'src/plugins/kibana_utils/public';
import { createVisualizeAppState } from './create_visualize_app_state';
import { migrateAppState } from './migrate_app_state';
import { visualizeAppStateStub } from './stubs';

const mockStartStateSync = jest.fn();
const mockStopStateSync = jest.fn();

jest.mock('../../../../kibana_utils/public', () => ({
createStateContainer: jest.fn(() => 'stateContainer'),
syncState: jest.fn(() => ({
start: mockStartStateSync,
stop: mockStopStateSync,
})),
}));
jest.mock('./migrate_app_state', () => ({
migrateAppState: jest.fn(() => 'migratedAppState'),
}));

const { createStateContainer, syncState } = jest.requireMock('../../../../kibana_utils/public');

describe('createVisualizeAppState', () => {
const kbnUrlStateStorage = ({
set: jest.fn(),
get: jest.fn(() => ({ linked: false })),
} as unknown) as IKbnUrlStateStorage;

const { stateContainer, stopStateSync } = createVisualizeAppState({
stateDefaults: visualizeAppStateStub,
kbnUrlStateStorage,
});
const transitions = createStateContainer.mock.calls[0][1];

test('should initialize visualize app state', () => {
expect(kbnUrlStateStorage.get).toHaveBeenCalledWith('_a');
expect(migrateAppState).toHaveBeenCalledWith({
...visualizeAppStateStub,
linked: false,
});
expect(kbnUrlStateStorage.set).toHaveBeenCalledWith('_a', 'migratedAppState', {
replace: true,
});
expect(createStateContainer).toHaveBeenCalled();
expect(syncState).toHaveBeenCalled();
expect(mockStartStateSync).toHaveBeenCalled();
});

test('should return the stateContainer and stopStateSync', () => {
expect(stateContainer).toBe('stateContainer');
stopStateSync();
expect(stopStateSync).toHaveBeenCalledTimes(1);
});

describe('stateContainer transitions', () => {
test('set', () => {
const newQuery = { query: '', language: '' };
expect(transitions.set(visualizeAppStateStub)('query', newQuery)).toEqual({
...visualizeAppStateStub,
query: newQuery,
});
});

test('setVis', () => {
const newVis = { data: 'data' };
expect(transitions.setVis(visualizeAppStateStub)(newVis)).toEqual({
...visualizeAppStateStub,
vis: {
...visualizeAppStateStub.vis,
...newVis,
},
});
});

test('unlinkSavedSearch', () => {
const params = {
query: { query: '', language: '' },
parentFilters: [{ test: 'filter2' }],
};
expect(transitions.unlinkSavedSearch(visualizeAppStateStub)(params)).toEqual({
...visualizeAppStateStub,
query: params.query,
filters: [...visualizeAppStateStub.filters, { test: 'filter2' }],
linked: false,
});
});

test('updateVisState: should not include resctricted param types', () => {
const newVisState = {
a: 1,
_b: 2,
$c: 3,
d: () => {},
};
expect(transitions.updateVisState(visualizeAppStateStub)(newVisState)).toEqual({
...visualizeAppStateStub,
vis: { a: 1 },
});
});

test('updateSavedQuery: add savedQuery', () => {
const savedQueryId = '123test';
expect(transitions.updateSavedQuery(visualizeAppStateStub)(savedQueryId)).toEqual({
...visualizeAppStateStub,
savedQuery: savedQueryId,
});
});

test('updateSavedQuery: remove savedQuery from state', () => {
const savedQueryId = '123test';
expect(
transitions.updateSavedQuery({ ...visualizeAppStateStub, savedQuery: savedQueryId })()
).toEqual(visualizeAppStateStub);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
* 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.
*/

import { createSavedSearchesLoader } from '../../../../discover/public';
import { getVisualizationInstance } from './get_visualization_instance';
import { createVisualizeServicesMock } from './mocks';
import { VisualizeServices } from '../types';
import { BehaviorSubject } from 'rxjs';

const mockSavedSearchObj = {};
const mockGetSavedSearch = jest.fn(() => mockSavedSearchObj);

jest.mock('../../../../discover/public', () => ({
createSavedSearchesLoader: jest.fn(() => ({
get: mockGetSavedSearch,
})),
}));

describe('getVisualizationInstance', () => {
const serializedVisMock = {
type: 'area',
};
let savedVisMock: any;
let visMock: any;
let mockServices: jest.Mocked<VisualizeServices>;
let subj: BehaviorSubject<any>;

beforeEach(() => {
mockServices = createVisualizeServicesMock();
subj = new BehaviorSubject({});
visMock = {
type: {},
data: {},
};
savedVisMock = {};
// @ts-expect-error
mockServices.savedVisualizations.get.mockImplementation(() => savedVisMock);
// @ts-expect-error
mockServices.visualizations.convertToSerializedVis.mockImplementation(() => serializedVisMock);
// @ts-expect-error
mockServices.visualizations.createVis.mockImplementation(() => visMock);
// @ts-expect-error
mockServices.createVisEmbeddableFromObject.mockImplementation(() => ({
getOutput$: jest.fn(() => subj.asObservable()),
}));
});

test('should create new instances of savedVis, vis and embeddableHandler', async () => {
const opts = {
type: 'area',
indexPattern: 'my_index_pattern',
};
const { savedVis, savedSearch, vis, embeddableHandler } = await getVisualizationInstance(
mockServices,
opts
);

expect(mockServices.savedVisualizations.get).toHaveBeenCalledWith(opts);
expect(savedVisMock.searchSourceFields).toEqual({
index: opts.indexPattern,
});
expect(mockServices.visualizations.convertToSerializedVis).toHaveBeenCalledWith(savedVisMock);
expect(mockServices.visualizations.createVis).toHaveBeenCalledWith(
serializedVisMock.type,
serializedVisMock
);
expect(mockServices.createVisEmbeddableFromObject).toHaveBeenCalledWith(visMock, {
timeRange: undefined,
filters: undefined,
id: '',
});

expect(vis).toBe(visMock);
expect(savedVis).toBe(savedVisMock);
expect(embeddableHandler).toBeDefined();
expect(savedSearch).toBeUndefined();
});

test('should load existing vis by id and call vis type setup if exists', async () => {
const newVisObj = { data: {} };
visMock.type.setup = jest.fn(() => newVisObj);
const { vis } = await getVisualizationInstance(mockServices, 'saved_vis_id');

expect(mockServices.savedVisualizations.get).toHaveBeenCalledWith('saved_vis_id');
expect(savedVisMock.searchSourceFields).toBeUndefined();
expect(visMock.type.setup).toHaveBeenCalledWith(visMock);
expect(vis).toBe(newVisObj);
});

test('should create saved search instance if vis based on saved search id', async () => {
visMock.data.savedSearchId = 'saved_search_id';
const { savedSearch } = await getVisualizationInstance(mockServices, 'saved_vis_id');

expect(createSavedSearchesLoader).toHaveBeenCalled();
expect(mockGetSavedSearch).toHaveBeenCalledWith(visMock.data.savedSearchId);
expect(savedSearch).toBe(mockSavedSearchObj);
});

test('should subscribe on embeddable handler updates and send toasts on errors', async () => {
await getVisualizationInstance(mockServices, 'saved_vis_id');

subj.next({
error: 'error',
});

expect(mockServices.toastNotifications.addError).toHaveBeenCalled();
});
});
43 changes: 43 additions & 0 deletions src/plugins/visualize/public/application/utils/mocks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* 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.
*/

import { coreMock } from '../../../../../core/public/mocks';
import { dataPluginMock } from '../../../../data/public/mocks';
import { visualizationsPluginMock } from '../../../../visualizations/public/mocks';
import { VisualizeServices } from '../types';

export const createVisualizeServicesMock = () => {
const coreStartMock = coreMock.createStart();
const dataStartMock = dataPluginMock.createStartContract();
const toastNotifications = coreStartMock.notifications.toasts;
const visualizations = visualizationsPluginMock.createStartContract();

return ({
...coreStartMock,
data: dataStartMock,
toastNotifications,
history: {
replace: jest.fn(),
location: { pathname: '' },
},
visualizations,
savedVisualizations: visualizations.savedVisualizationsLoader,
createVisEmbeddableFromObject: visualizations.__LEGACY.createVisEmbeddableFromObject,
} as unknown) as jest.Mocked<VisualizeServices>;
};
Loading

0 comments on commit 1c91b1c

Please sign in to comment.