Skip to content

Commit 1c91b1c

Browse files
[Visualize] Add unit tests (#70410)
* 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>
1 parent 0ebddcf commit 1c91b1c

File tree

13 files changed

+1331
-4
lines changed

13 files changed

+1331
-4
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
import { Observable } from 'rxjs';
21+
import { FilterManager } from './filter_manager';
22+
23+
export const createFilterManagerMock = () => {
24+
const filterManager = ({
25+
mergeIncomingFilters: jest.fn(),
26+
handleStateUpdate: jest.fn(),
27+
getFilters: jest.fn(),
28+
getAppFilters: jest.fn(),
29+
getGlobalFilters: jest.fn(),
30+
getPartitionedFilters: jest.fn(),
31+
getUpdates$: jest.fn(() => new Observable()),
32+
getFetches$: jest.fn(() => new Observable()),
33+
addFilters: jest.fn(),
34+
setFilters: jest.fn(),
35+
setGlobalFilters: jest.fn(),
36+
setAppFilters: jest.fn(),
37+
removeFilter: jest.fn(),
38+
removeAll: jest.fn(),
39+
} as unknown) as jest.Mocked<FilterManager>;
40+
41+
return filterManager;
42+
};

src/plugins/data/public/query/mocks.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,13 @@
2020
import { Observable } from 'rxjs';
2121
import { QueryService, QuerySetup, QueryStart } from '.';
2222
import { timefilterServiceMock } from './timefilter/timefilter_service.mock';
23+
import { createFilterManagerMock } from './filter_manager/filter_manager.mock';
2324

2425
type QueryServiceClientContract = PublicMethodsOf<QueryService>;
2526

2627
const createSetupContractMock = () => {
2728
const setupContract: jest.Mocked<QuerySetup> = {
28-
filterManager: jest.fn() as any,
29+
filterManager: createFilterManagerMock(),
2930
timefilter: timefilterServiceMock.createSetupContract(),
3031
state$: new Observable(),
3132
};
@@ -36,7 +37,7 @@ const createSetupContractMock = () => {
3637
const createStartContractMock = () => {
3738
const startContract: jest.Mocked<QueryStart> = {
3839
addToQueryLog: jest.fn(),
39-
filterManager: jest.fn() as any,
40+
filterManager: createFilterManagerMock(),
4041
savedQueries: jest.fn() as any,
4142
state$: new Observable(),
4243
timefilter: timefilterServiceMock.createStartContract(),

src/plugins/visualizations/public/mocks.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ const createStartContract = (): VisualizationsStart => ({
3939
get: jest.fn(),
4040
all: jest.fn(),
4141
getAliases: jest.fn(),
42-
savedVisualizationsLoader: {} as any,
42+
savedVisualizationsLoader: {
43+
get: jest.fn(),
44+
} as any,
4345
showNewVisModal: jest.fn(),
4446
createVis: jest.fn(),
4547
convertFromSerializedVis: jest.fn(),

src/plugins/visualize/public/application/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export type PureVisState = SavedVisState;
5050

5151
export interface VisualizeAppState {
5252
filters: Filter[];
53-
uiState: PersistedState;
53+
uiState: Record<string, unknown>;
5454
vis: PureVisState;
5555
query: Query;
5656
savedQuery?: string;
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
import { IKbnUrlStateStorage } from 'src/plugins/kibana_utils/public';
21+
import { createVisualizeAppState } from './create_visualize_app_state';
22+
import { migrateAppState } from './migrate_app_state';
23+
import { visualizeAppStateStub } from './stubs';
24+
25+
const mockStartStateSync = jest.fn();
26+
const mockStopStateSync = jest.fn();
27+
28+
jest.mock('../../../../kibana_utils/public', () => ({
29+
createStateContainer: jest.fn(() => 'stateContainer'),
30+
syncState: jest.fn(() => ({
31+
start: mockStartStateSync,
32+
stop: mockStopStateSync,
33+
})),
34+
}));
35+
jest.mock('./migrate_app_state', () => ({
36+
migrateAppState: jest.fn(() => 'migratedAppState'),
37+
}));
38+
39+
const { createStateContainer, syncState } = jest.requireMock('../../../../kibana_utils/public');
40+
41+
describe('createVisualizeAppState', () => {
42+
const kbnUrlStateStorage = ({
43+
set: jest.fn(),
44+
get: jest.fn(() => ({ linked: false })),
45+
} as unknown) as IKbnUrlStateStorage;
46+
47+
const { stateContainer, stopStateSync } = createVisualizeAppState({
48+
stateDefaults: visualizeAppStateStub,
49+
kbnUrlStateStorage,
50+
});
51+
const transitions = createStateContainer.mock.calls[0][1];
52+
53+
test('should initialize visualize app state', () => {
54+
expect(kbnUrlStateStorage.get).toHaveBeenCalledWith('_a');
55+
expect(migrateAppState).toHaveBeenCalledWith({
56+
...visualizeAppStateStub,
57+
linked: false,
58+
});
59+
expect(kbnUrlStateStorage.set).toHaveBeenCalledWith('_a', 'migratedAppState', {
60+
replace: true,
61+
});
62+
expect(createStateContainer).toHaveBeenCalled();
63+
expect(syncState).toHaveBeenCalled();
64+
expect(mockStartStateSync).toHaveBeenCalled();
65+
});
66+
67+
test('should return the stateContainer and stopStateSync', () => {
68+
expect(stateContainer).toBe('stateContainer');
69+
stopStateSync();
70+
expect(stopStateSync).toHaveBeenCalledTimes(1);
71+
});
72+
73+
describe('stateContainer transitions', () => {
74+
test('set', () => {
75+
const newQuery = { query: '', language: '' };
76+
expect(transitions.set(visualizeAppStateStub)('query', newQuery)).toEqual({
77+
...visualizeAppStateStub,
78+
query: newQuery,
79+
});
80+
});
81+
82+
test('setVis', () => {
83+
const newVis = { data: 'data' };
84+
expect(transitions.setVis(visualizeAppStateStub)(newVis)).toEqual({
85+
...visualizeAppStateStub,
86+
vis: {
87+
...visualizeAppStateStub.vis,
88+
...newVis,
89+
},
90+
});
91+
});
92+
93+
test('unlinkSavedSearch', () => {
94+
const params = {
95+
query: { query: '', language: '' },
96+
parentFilters: [{ test: 'filter2' }],
97+
};
98+
expect(transitions.unlinkSavedSearch(visualizeAppStateStub)(params)).toEqual({
99+
...visualizeAppStateStub,
100+
query: params.query,
101+
filters: [...visualizeAppStateStub.filters, { test: 'filter2' }],
102+
linked: false,
103+
});
104+
});
105+
106+
test('updateVisState: should not include resctricted param types', () => {
107+
const newVisState = {
108+
a: 1,
109+
_b: 2,
110+
$c: 3,
111+
d: () => {},
112+
};
113+
expect(transitions.updateVisState(visualizeAppStateStub)(newVisState)).toEqual({
114+
...visualizeAppStateStub,
115+
vis: { a: 1 },
116+
});
117+
});
118+
119+
test('updateSavedQuery: add savedQuery', () => {
120+
const savedQueryId = '123test';
121+
expect(transitions.updateSavedQuery(visualizeAppStateStub)(savedQueryId)).toEqual({
122+
...visualizeAppStateStub,
123+
savedQuery: savedQueryId,
124+
});
125+
});
126+
127+
test('updateSavedQuery: remove savedQuery from state', () => {
128+
const savedQueryId = '123test';
129+
expect(
130+
transitions.updateSavedQuery({ ...visualizeAppStateStub, savedQuery: savedQueryId })()
131+
).toEqual(visualizeAppStateStub);
132+
});
133+
});
134+
});
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
import { createSavedSearchesLoader } from '../../../../discover/public';
21+
import { getVisualizationInstance } from './get_visualization_instance';
22+
import { createVisualizeServicesMock } from './mocks';
23+
import { VisualizeServices } from '../types';
24+
import { BehaviorSubject } from 'rxjs';
25+
26+
const mockSavedSearchObj = {};
27+
const mockGetSavedSearch = jest.fn(() => mockSavedSearchObj);
28+
29+
jest.mock('../../../../discover/public', () => ({
30+
createSavedSearchesLoader: jest.fn(() => ({
31+
get: mockGetSavedSearch,
32+
})),
33+
}));
34+
35+
describe('getVisualizationInstance', () => {
36+
const serializedVisMock = {
37+
type: 'area',
38+
};
39+
let savedVisMock: any;
40+
let visMock: any;
41+
let mockServices: jest.Mocked<VisualizeServices>;
42+
let subj: BehaviorSubject<any>;
43+
44+
beforeEach(() => {
45+
mockServices = createVisualizeServicesMock();
46+
subj = new BehaviorSubject({});
47+
visMock = {
48+
type: {},
49+
data: {},
50+
};
51+
savedVisMock = {};
52+
// @ts-expect-error
53+
mockServices.savedVisualizations.get.mockImplementation(() => savedVisMock);
54+
// @ts-expect-error
55+
mockServices.visualizations.convertToSerializedVis.mockImplementation(() => serializedVisMock);
56+
// @ts-expect-error
57+
mockServices.visualizations.createVis.mockImplementation(() => visMock);
58+
// @ts-expect-error
59+
mockServices.createVisEmbeddableFromObject.mockImplementation(() => ({
60+
getOutput$: jest.fn(() => subj.asObservable()),
61+
}));
62+
});
63+
64+
test('should create new instances of savedVis, vis and embeddableHandler', async () => {
65+
const opts = {
66+
type: 'area',
67+
indexPattern: 'my_index_pattern',
68+
};
69+
const { savedVis, savedSearch, vis, embeddableHandler } = await getVisualizationInstance(
70+
mockServices,
71+
opts
72+
);
73+
74+
expect(mockServices.savedVisualizations.get).toHaveBeenCalledWith(opts);
75+
expect(savedVisMock.searchSourceFields).toEqual({
76+
index: opts.indexPattern,
77+
});
78+
expect(mockServices.visualizations.convertToSerializedVis).toHaveBeenCalledWith(savedVisMock);
79+
expect(mockServices.visualizations.createVis).toHaveBeenCalledWith(
80+
serializedVisMock.type,
81+
serializedVisMock
82+
);
83+
expect(mockServices.createVisEmbeddableFromObject).toHaveBeenCalledWith(visMock, {
84+
timeRange: undefined,
85+
filters: undefined,
86+
id: '',
87+
});
88+
89+
expect(vis).toBe(visMock);
90+
expect(savedVis).toBe(savedVisMock);
91+
expect(embeddableHandler).toBeDefined();
92+
expect(savedSearch).toBeUndefined();
93+
});
94+
95+
test('should load existing vis by id and call vis type setup if exists', async () => {
96+
const newVisObj = { data: {} };
97+
visMock.type.setup = jest.fn(() => newVisObj);
98+
const { vis } = await getVisualizationInstance(mockServices, 'saved_vis_id');
99+
100+
expect(mockServices.savedVisualizations.get).toHaveBeenCalledWith('saved_vis_id');
101+
expect(savedVisMock.searchSourceFields).toBeUndefined();
102+
expect(visMock.type.setup).toHaveBeenCalledWith(visMock);
103+
expect(vis).toBe(newVisObj);
104+
});
105+
106+
test('should create saved search instance if vis based on saved search id', async () => {
107+
visMock.data.savedSearchId = 'saved_search_id';
108+
const { savedSearch } = await getVisualizationInstance(mockServices, 'saved_vis_id');
109+
110+
expect(createSavedSearchesLoader).toHaveBeenCalled();
111+
expect(mockGetSavedSearch).toHaveBeenCalledWith(visMock.data.savedSearchId);
112+
expect(savedSearch).toBe(mockSavedSearchObj);
113+
});
114+
115+
test('should subscribe on embeddable handler updates and send toasts on errors', async () => {
116+
await getVisualizationInstance(mockServices, 'saved_vis_id');
117+
118+
subj.next({
119+
error: 'error',
120+
});
121+
122+
expect(mockServices.toastNotifications.addError).toHaveBeenCalled();
123+
});
124+
});
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
import { coreMock } from '../../../../../core/public/mocks';
21+
import { dataPluginMock } from '../../../../data/public/mocks';
22+
import { visualizationsPluginMock } from '../../../../visualizations/public/mocks';
23+
import { VisualizeServices } from '../types';
24+
25+
export const createVisualizeServicesMock = () => {
26+
const coreStartMock = coreMock.createStart();
27+
const dataStartMock = dataPluginMock.createStartContract();
28+
const toastNotifications = coreStartMock.notifications.toasts;
29+
const visualizations = visualizationsPluginMock.createStartContract();
30+
31+
return ({
32+
...coreStartMock,
33+
data: dataStartMock,
34+
toastNotifications,
35+
history: {
36+
replace: jest.fn(),
37+
location: { pathname: '' },
38+
},
39+
visualizations,
40+
savedVisualizations: visualizations.savedVisualizationsLoader,
41+
createVisEmbeddableFromObject: visualizations.__LEGACY.createVisEmbeddableFromObject,
42+
} as unknown) as jest.Mocked<VisualizeServices>;
43+
};

0 commit comments

Comments
 (0)