From 1b8954c1408ee007139d674fc354806398287966 Mon Sep 17 00:00:00 2001 From: Jason Stoltzfus Date: Fri, 14 May 2021 09:27:50 -0400 Subject: [PATCH 1/6] Added a kea test helper --- .../public/applications/__mocks__/kea.mock.ts | 30 +++++++ .../search_ui/search_ui_logic.test.ts | 89 ++++++++++--------- 2 files changed, 75 insertions(+), 44 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea.mock.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea.mock.ts index 4ebb9edd20c0e2..add4351065fb6f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea.mock.ts +++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea.mock.ts @@ -132,6 +132,36 @@ export class LogicMounter { // built logic instance with props, NOT the unmount fn }; + // Custom "jest-like" assertions + // ex. + // expectAction(() => { + // SomeLogic.actions.dataInitialized(); + // }).toChangeState({ + // from: { dataLoading: true }, + // to: { dataLoading: false }, + // }); + public expectAction = (action: any) => { + return { + // Mount state with "from" values and test that the specified "to" values are present in + // the updated state, and that no other values have changed. + toChangeState: ({ from, to }: { from: object; to: object }, ignoreFields: string[] = []) => { + this.mount(from); + const originalValues = { + ...this.logicFile.values, + }; + action(); + expect(this.logicFile.values).toEqual({ + ...originalValues, + ...to, + ...ignoreFields.reduce((acc: Record, field: string) => { + acc[field] = expect.anything(); + return acc; + }, {}), + }); + }, + }; + }; + // Also add unmount as a class method that can be destructured on init without becoming stale later public unmount = () => { this.unmountFn(); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui_logic.test.ts index 2191c633131bb2..9484a68e00803a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui_logic.test.ts @@ -16,7 +16,7 @@ import { ActiveField } from './types'; import { SearchUILogic } from './'; describe('SearchUILogic', () => { - const { mount } = new LogicMounter(SearchUILogic); + const { mount, expectAction } = new LogicMounter(SearchUILogic); const { http } = mockHttpValues; const { flashAPIErrors } = mockFlashMessageHelpers; @@ -45,79 +45,80 @@ describe('SearchUILogic', () => { describe('actions', () => { describe('onFieldDataLoaded', () => { it('sets initial field values fetched from API call and sets dataLoading to false', () => { - mount({ - validFields: [], - validSortFields: [], - validFacetFields: [], - }); - - SearchUILogic.actions.onFieldDataLoaded({ - validFields: ['foo'], - validSortFields: ['bar'], - validFacetFields: ['baz'], - }); - - expect(SearchUILogic.values).toEqual({ - ...DEFAULT_VALUES, - dataLoading: false, - validFields: ['foo'], - validSortFields: ['bar'], - validFacetFields: ['baz'], + expectAction(() => { + SearchUILogic.actions.onFieldDataLoaded({ + validFields: ['foo'], + validSortFields: ['bar'], + validFacetFields: ['baz'], + }); + }).toChangeState({ + from: { + dataLoading: true, + validFields: [], + validSortFields: [], + validFacetFields: [], + }, + to: { + dataLoading: false, + validFields: ['foo'], + validSortFields: ['bar'], + validFacetFields: ['baz'], + }, }); }); }); describe('onTitleFieldChange', () => { it('sets the titleField value', () => { - mount({ titleField: '' }); - SearchUILogic.actions.onTitleFieldChange('foo'); - expect(SearchUILogic.values).toEqual({ - ...DEFAULT_VALUES, - titleField: 'foo', + expectAction(() => { + SearchUILogic.actions.onTitleFieldChange('foo'); + }).toChangeState({ + from: { titleField: '' }, + to: { titleField: 'foo' }, }); }); }); describe('onUrlFieldChange', () => { it('sets the urlField value', () => { - mount({ urlField: '' }); - SearchUILogic.actions.onUrlFieldChange('foo'); - expect(SearchUILogic.values).toEqual({ - ...DEFAULT_VALUES, - urlField: 'foo', + expectAction(() => { + SearchUILogic.actions.onUrlFieldChange('foo'); + }).toChangeState({ + from: { urlField: '' }, + to: { urlField: 'foo' }, }); }); }); describe('onFacetFieldsChange', () => { it('sets the facetFields value', () => { - mount({ facetFields: [] }); - SearchUILogic.actions.onFacetFieldsChange(['foo']); - expect(SearchUILogic.values).toEqual({ - ...DEFAULT_VALUES, - facetFields: ['foo'], + expectAction(() => { + SearchUILogic.actions.onFacetFieldsChange(['foo']); + }).toChangeState({ + from: { facetFields: [] }, + to: { facetFields: ['foo'] }, }); }); }); describe('onSortFieldsChange', () => { it('sets the sortFields value', () => { - mount({ sortFields: [] }); - SearchUILogic.actions.onSortFieldsChange(['foo']); - expect(SearchUILogic.values).toEqual({ - ...DEFAULT_VALUES, - sortFields: ['foo'], + expectAction(() => { + SearchUILogic.actions.onSortFieldsChange(['foo']); + }).toChangeState({ + from: { sortFields: [] }, + to: { sortFields: ['foo'] }, }); }); }); describe('onActiveFieldChange', () => { it('sets the activeField value', () => { - mount({ activeField: '' }); - SearchUILogic.actions.onActiveFieldChange(ActiveField.Sort); - expect(SearchUILogic.values).toEqual({ - ...DEFAULT_VALUES, - activeField: ActiveField.Sort, + expectAction(() => { + SearchUILogic.actions.onActiveFieldChange(ActiveField.Sort); + }).toChangeState({ + from: { activeField: '' }, + to: { activeField: ActiveField.Sort }, }); }); }); From 49fe60abbdcbce188f95d623906554f0c9771c25 Mon Sep 17 00:00:00 2001 From: Jason Stoltzfus Date: Fri, 14 May 2021 11:46:41 -0400 Subject: [PATCH 2/6] Handle connected values --- .../public/applications/__mocks__/kea.mock.ts | 95 +++++++++++--- .../components/schema/schema_logic.test.ts | 118 ++++++++---------- 2 files changed, 132 insertions(+), 81 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea.mock.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea.mock.ts index add4351065fb6f..54c110fd39cde8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea.mock.ts +++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea.mock.ts @@ -85,6 +85,7 @@ export const setMockActions = (actions: object) => { * }); */ import { resetContext, LogicWrapper } from 'kea'; +import { merge } from 'lodash'; type LogicFile = LogicWrapper; @@ -101,25 +102,82 @@ export class LogicMounter { if (!values || !Object.keys(values).length) { resetContext({}); } else { - let { path, key } = this.logicFile.inputs[0]; - - // For keyed logic files, both key and path should be functions - if (this.logicFile._isKeaWithKey) { - key = key(props); - path = path(key); - } - - // Generate the correct nested defaults obj based on the file path - // example path: ['x', 'y', 'z'] - // example defaults: { x: { y: { z: values } } } - const defaults = path.reduceRight( - (value: object, name: string) => ({ [name]: value }), - values - ); + const defaults: object = this.createDefaultValuesObject(values, props); resetContext({ defaults }); } }; + /** + * Based on the values passed into mount, turn them into properly nested objects that can + * be passed to kea's resetContext in order to set default values": + * + * ex. + * + * input: + * + * values: { + * schema: { foo: "text" }, + * engineName: "engine1" + * } + * + * output: + * + * { + * enterprise_search: { + * app_search: { + * schema_logic: { + * schema: { foo: "text" } + * }, + * engine_logic: { + * engineName: "engine1" + * } + * } + * } + * } + */ + private createDefaultValuesObject = (values: object, props?: object) => { + let { path, key } = this.logicFile.inputs[0]; + + // For keyed logic files, both key and path should be functions + if (this.logicFile._isKeaWithKey) { + key = key(props); + path = path(key); + } + + // TODO Deal with this if and when we get there. + if (this.logicFile.inputs[0].connect?.values.length > 2) { + throw Error( + "This connected logic has more than 2 values in 'connect', implement handler logic for this in kea.mock.ts" + ); + } + + // If a logic includes values from another logic via the "connect" property, we need to make sure they're nested + // correctly under the correct path. + // + // For example, if the current logic under test is SchemaLogic connects values from EngineLogic also, then we need + // to make sure that values from SchemaLogic get nested under enterprise_search.app_search.schema_logic, and values + // from EngineLogic get nested under enterprise_search.app_search.engine_logic + if (this.logicFile.inputs[0].connect?.values[0]) { + const connectedPath = this.logicFile.inputs[0].connect.values[0].inputs[0].path; + const connectedValueKeys = this.logicFile.inputs[0].connect.values[1]; + + const primaryValues: Record = {}; + const connectedValues: Record = {}; + + Object.entries(values).forEach(([k, v]) => { + if (connectedValueKeys.includes(k)) { + connectedValues[k] = v; + } else { + primaryValues[k] = v; + } + }); + + return merge(createDefaults(path, values), createDefaults(connectedPath, connectedValues)); + } else { + return createDefaults(path, values); + } + }; + // Automatically reset context & mount the logic file public mount = (values?: object, props?: object) => { this.resetContext(values, props); @@ -192,3 +250,10 @@ export class LogicMounter { : listeners; // handles simpler logic files that just define listeners: { ... } }; } + +// Generate the correct nested defaults obj based on the file path +// example path: ['x', 'y', 'z'] +// example defaults: { x: { y: { z: values } } } +const createDefaults = (path: string[], values: object) => { + return path.reduceRight((value: object, name: string) => ({ [name]: value }), values); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_logic.test.ts index 123f62af4eebad..f0da5c323e03b2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_logic.test.ts @@ -10,12 +10,12 @@ import { mockEngineActions } from '../../__mocks__/engine_logic.mock'; import { nextTick } from '@kbn/test/jest'; -import { SchemaType, Schema } from '../../../shared/schema/types'; +import { SchemaType } from '../../../shared/schema/types'; import { SchemaLogic } from './schema_logic'; describe('SchemaLogic', () => { - const { mount } = new LogicMounter(SchemaLogic); + const { mount, expectAction } = new LogicMounter(SchemaLogic); const { http } = mockHttpValues; const { flashAPIErrors, flashSuccessToast, setErrorMessage } = mockFlashMessageHelpers; @@ -49,16 +49,6 @@ describe('SchemaLogic', () => { isModalOpen: false, }; - /* - * Unfortunately, we can't mount({ schema: ... }) & have to use an action to set schema - * because of the separate connected logic file - our LogicMounter test helper sets context - * for only the currently tested file - */ - const mountAndSetSchema = ({ schema, ...values }: { schema: Schema; [key: string]: unknown }) => { - mount(values); - SchemaLogic.actions.setSchema(schema); - }; - beforeEach(() => { jest.clearAllMocks(); }); @@ -71,64 +61,63 @@ describe('SchemaLogic', () => { describe('actions', () => { describe('onSchemaLoad', () => { it('stores the API response in state and sets isUpdating & isModalOpen to false', () => { - mount({ isModalOpen: true }); - - SchemaLogic.actions.onSchemaLoad(MOCK_RESPONSE); - - expect(SchemaLogic.values).toEqual({ - ...DEFAULT_VALUES, - // SchemaBaseLogic - dataLoading: false, - schema: MOCK_RESPONSE.schema, - // SchemaLogic - isUpdating: false, - isModalOpen: false, - cachedSchema: MOCK_RESPONSE.schema, - hasSchema: true, - mostRecentIndexJob: MOCK_RESPONSE.mostRecentIndexJob, - unconfirmedFields: MOCK_RESPONSE.unconfirmedFields, - hasUnconfirmedFields: true, - hasNewUnsearchedFields: MOCK_RESPONSE.unsearchedUnconfirmedFields, + expectAction(() => { + SchemaLogic.actions.onSchemaLoad(MOCK_RESPONSE); + }).toChangeState({ + from: { isModalOpen: true }, + to: { + // SchemaBaseLogic + dataLoading: false, + schema: MOCK_RESPONSE.schema, + // SchemaLogic + isUpdating: false, + isModalOpen: false, + cachedSchema: MOCK_RESPONSE.schema, + hasSchema: true, + mostRecentIndexJob: MOCK_RESPONSE.mostRecentIndexJob, + unconfirmedFields: MOCK_RESPONSE.unconfirmedFields, + hasUnconfirmedFields: true, + hasNewUnsearchedFields: MOCK_RESPONSE.unsearchedUnconfirmedFields, + }, }); }); }); describe('onSchemaUpdateError', () => { it('sets isUpdating & isModalOpen to false', () => { - mount({ isUpdating: true, isModalOpen: true }); - - SchemaLogic.actions.onSchemaUpdateError(); - - expect(SchemaLogic.values).toEqual({ - ...DEFAULT_VALUES, - isUpdating: false, - isModalOpen: false, + expectAction(() => { + SchemaLogic.actions.onSchemaUpdateError(); + }).toChangeState({ + from: { + isUpdating: true, + isModalOpen: true, + }, + to: { + isUpdating: false, + isModalOpen: false, + }, }); }); }); describe('openModal', () => { it('sets isModalOpen to true', () => { - mount({ isModalOpen: false }); - - SchemaLogic.actions.openModal(); - - expect(SchemaLogic.values).toEqual({ - ...DEFAULT_VALUES, - isModalOpen: true, + expectAction(() => { + SchemaLogic.actions.openModal(); + }).toChangeState({ + from: { isModalOpen: false }, + to: { isModalOpen: true }, }); }); }); describe('closeModal', () => { it('sets isModalOpen to false', () => { - mount({ isModalOpen: true }); - - SchemaLogic.actions.closeModal(); - - expect(SchemaLogic.values).toEqual({ - ...DEFAULT_VALUES, - isModalOpen: false, + expectAction(() => { + SchemaLogic.actions.closeModal(); + }).toChangeState({ + from: { isModalOpen: true }, + to: { isModalOpen: false }, }); }); }); @@ -137,19 +126,19 @@ describe('SchemaLogic', () => { describe('selectors', () => { describe('hasSchema', () => { it('returns true when the schema obj has items', () => { - mountAndSetSchema({ schema: { test: SchemaType.Text } }); + mount({ schema: { test: SchemaType.Text } }); expect(SchemaLogic.values.hasSchema).toEqual(true); }); it('returns false when the schema obj is empty', () => { - mountAndSetSchema({ schema: {} }); + mount({ schema: {} }); expect(SchemaLogic.values.hasSchema).toEqual(false); }); }); describe('hasSchemaChanged', () => { it('returns true when the schema state is different from the cachedSchema state', () => { - mountAndSetSchema({ + mount({ schema: { test: SchemaType.Text }, cachedSchema: { test: SchemaType.Number }, }); @@ -158,11 +147,10 @@ describe('SchemaLogic', () => { }); it('returns false when the stored schema is the same as cachedSchema', () => { - mountAndSetSchema({ + mount({ schema: { test: SchemaType.Text }, cachedSchema: { test: SchemaType.Text }, }); - expect(SchemaLogic.values.hasSchemaChanged).toEqual(false); }); }); @@ -184,7 +172,7 @@ describe('SchemaLogic', () => { describe('addSchemaField', () => { describe('if the schema field already exists', () => { it('flashes an error and closes the modal', () => { - mountAndSetSchema({ schema: { existing_field: SchemaType.Text } }); + mount({ schema: { existing_field: SchemaType.Text } }); jest.spyOn(SchemaLogic.actions, 'closeModal'); SchemaLogic.actions.addSchemaField('existing_field', SchemaType.Text); @@ -214,7 +202,7 @@ describe('SchemaLogic', () => { describe('updateSchemaFieldType', () => { it("updates an existing schema key's field type value", async () => { - mountAndSetSchema({ schema: { existing_field: SchemaType.Text } }); + mount({ schema: { existing_field: SchemaType.Text } }); jest.spyOn(SchemaLogic.actions, 'setSchema'); SchemaLogic.actions.updateSchemaFieldType('existing_field', SchemaType.Geolocation); @@ -227,13 +215,11 @@ describe('SchemaLogic', () => { describe('updateSchema', () => { it('sets isUpdating to true', () => { - mount({ isUpdating: false }); - - SchemaLogic.actions.updateSchema(); - - expect(SchemaLogic.values).toEqual({ - ...DEFAULT_VALUES, - isUpdating: true, + expectAction(() => { + SchemaLogic.actions.updateSchema(); + }).toChangeState({ + from: { isUpdating: false }, + to: { isUpdating: true }, }); }); From f0841951b6da5fb21bee5a7b5e5e31536debaa48 Mon Sep 17 00:00:00 2001 From: Jason Stoltzfus Date: Fri, 14 May 2021 12:13:24 -0400 Subject: [PATCH 3/6] Handle keyed logic --- .../public/applications/__mocks__/kea.mock.ts | 22 +++-- .../multi_input_rows_logic.test.ts | 85 ++++++++++--------- 2 files changed, 60 insertions(+), 47 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea.mock.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea.mock.ts index 54c110fd39cde8..0dde75382926b9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea.mock.ts +++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea.mock.ts @@ -198,17 +198,29 @@ export class LogicMounter { // from: { dataLoading: true }, // to: { dataLoading: false }, // }); - public expectAction = (action: any) => { + // + // For keyed logic: + // + // ex. + // expectAction((logic) => { + // logic.actions.dataInitialized(); + // }, PROPS).toChangeState({ + // from: { dataLoading: true }, + // to: { dataLoading: false }, + // }); + // + + public expectAction = (action: (logic: LogicFile) => void, props: object = {}) => { return { // Mount state with "from" values and test that the specified "to" values are present in // the updated state, and that no other values have changed. toChangeState: ({ from, to }: { from: object; to: object }, ignoreFields: string[] = []) => { - this.mount(from); + const logic = this.mount(from, props); const originalValues = { - ...this.logicFile.values, + ...logic.values, }; - action(); - expect(this.logicFile.values).toEqual({ + action(logic); + expect(logic.values).toEqual({ ...originalValues, ...to, ...ignoreFields.reduce((acc: Record, field: string) => { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/multi_input_rows_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/multi_input_rows_logic.test.ts index 0e2d28a6fc3e1f..79b1d6a1caba2f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/multi_input_rows_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/multi_input_rows_logic.test.ts @@ -7,12 +7,10 @@ import { LogicMounter } from '../../../__mocks__'; -import { Logic } from 'kea'; - import { MultiInputRowsLogic } from './multi_input_rows_logic'; describe('MultiInputRowsLogic', () => { - const { mount } = new LogicMounter(MultiInputRowsLogic); + const { mount, expectAction } = new LogicMounter(MultiInputRowsLogic); const MOCK_VALUES = ['a', 'b', 'c']; @@ -37,67 +35,70 @@ describe('MultiInputRowsLogic', () => { }); describe('actions', () => { - let logic: Logic; - - beforeEach(() => { - logic = mount({}, DEFAULT_PROPS); - }); - - afterEach(() => { - // Should not mutate the original array - expect(logic.values.values).not.toBe(MOCK_VALUES); // Would fail if we did not clone a new array - }); - describe('addValue', () => { it('appends an empty string to the values array & sets addedNewRow to true', () => { - logic.actions.addValue(); - - expect(logic.values).toEqual({ - ...DEFAULT_VALUES, - addedNewRow: true, - hasEmptyValues: true, - values: ['a', 'b', 'c', ''], + expectAction((logic) => { + logic.actions.addValue(); + }, DEFAULT_PROPS).toChangeState({ + from: { + addedNewRow: false, + hasEmptyValues: false, + values: ['a', 'b', 'c'], + }, + to: { + addedNewRow: true, + hasEmptyValues: true, + values: ['a', 'b', 'c', ''], + }, }); }); }); describe('deleteValue', () => { it('deletes the value at the specified array index', () => { - logic.actions.deleteValue(1); - - expect(logic.values).toEqual({ - ...DEFAULT_VALUES, - values: ['a', 'c'], + expectAction((logic) => { + logic.actions.deleteValue(1); + }, DEFAULT_PROPS).toChangeState({ + from: { + values: ['a', 'b', 'c'], + }, + to: { + values: ['a', 'c'], + }, }); }); }); describe('editValue', () => { it('edits the value at the specified array index', () => { - logic.actions.editValue(2, 'z'); - - expect(logic.values).toEqual({ - ...DEFAULT_VALUES, - values: ['a', 'b', 'z'], + expectAction((logic) => { + logic.actions.editValue(2, 'z'); + }, DEFAULT_PROPS).toChangeState({ + from: { + values: ['a', 'b', 'c'], + }, + to: { + values: ['a', 'b', 'z'], + }, }); }); }); - }); - describe('selectors', () => { - describe('hasEmptyValues', () => { - it('returns true if values has any empty strings', () => { - const logic = mount({}, { ...DEFAULT_PROPS, values: ['', '', ''] }); + describe('selectors', () => { + describe('hasEmptyValues', () => { + it('returns true if values has any empty strings', () => { + const logic = mount({}, { ...DEFAULT_PROPS, values: ['', '', ''] }); - expect(logic.values.hasEmptyValues).toEqual(true); + expect(logic.values.hasEmptyValues).toEqual(true); + }); }); - }); - describe('hasOnlyOneValue', () => { - it('returns true if values only has one item', () => { - const logic = mount({}, { ...DEFAULT_PROPS, values: ['test'] }); + describe('hasOnlyOneValue', () => { + it('returns true if values only has one item', () => { + const logic = mount({}, { ...DEFAULT_PROPS, values: ['test'] }); - expect(logic.values.hasOnlyOneValue).toEqual(true); + expect(logic.values.hasOnlyOneValue).toEqual(true); + }); }); }); }); From c3466687a73afdbe845d0a40910286ae1403e54a Mon Sep 17 00:00:00 2001 From: Jason Stoltzfus Date: Fri, 14 May 2021 12:39:52 -0400 Subject: [PATCH 4/6] Ignore certain field changes --- .../public/applications/__mocks__/kea.mock.ts | 4 +- .../result_settings_logic.test.ts | 246 +++++++++--------- 2 files changed, 120 insertions(+), 130 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea.mock.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea.mock.ts index 0dde75382926b9..0926c67544701a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea.mock.ts +++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea.mock.ts @@ -214,7 +214,7 @@ export class LogicMounter { return { // Mount state with "from" values and test that the specified "to" values are present in // the updated state, and that no other values have changed. - toChangeState: ({ from, to }: { from: object; to: object }, ignoreFields: string[] = []) => { + toChangeState: ({ from, to, ignore }: { from: object; to: object; ignore?: string[] }) => { const logic = this.mount(from, props); const originalValues = { ...logic.values, @@ -223,7 +223,7 @@ export class LogicMounter { expect(logic.values).toEqual({ ...originalValues, ...to, - ...ignoreFields.reduce((acc: Record, field: string) => { + ...(ignore || []).reduce((acc: Record, field: string) => { acc[field] = expect.anything(); return acc; }, {}), diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.test.ts index 6522d84aef1565..fc3b4acb91ad90 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.test.ts @@ -32,7 +32,7 @@ const expectToHaveBeenCalledWithStrict = ( }; describe('ResultSettingsLogic', () => { - const { mount } = new LogicMounter(ResultSettingsLogic); + const { mount, expectAction } = new LogicMounter(ResultSettingsLogic); const DEFAULT_VALUES = { dataLoading: true, @@ -54,6 +54,8 @@ describe('ResultSettingsLogic', () => { queryPerformanceScore: 0, }; + const SELECTOR_FIELDS = Object.keys(SELECTORS); + // Values without selectors const resultSettingLogicValues = () => omit(ResultSettingsLogic.values, Object.keys(SELECTORS)); @@ -91,117 +93,112 @@ describe('ResultSettingsLogic', () => { }; it('will initialize all result field state within the UI, based on provided server data', () => { - mount({ - dataLoading: true, - saving: true, - }); - - ResultSettingsLogic.actions.initializeResultFields( - serverResultFields, - schema, - schemaConflicts - ); - - expect(resultSettingLogicValues()).toEqual({ - ...DEFAULT_VALUES, - dataLoading: false, - saving: false, - // It converts the passed server result fields to a client results field and stores it - // as resultFields - resultFields: { - foo: { - raw: true, - rawSize: 5, - snippet: false, - snippetFallback: false, - }, - // Baz was not part of the original serverResultFields, it was injected based on the schema - baz: { - raw: false, - snippet: false, - snippetFallback: false, - }, - bar: { - raw: true, - rawSize: 5, - snippet: false, - snippetFallback: false, - }, - }, - // It also saves it as lastSavedResultFields - lastSavedResultFields: { - foo: { - raw: true, - rawSize: 5, - snippet: false, - snippetFallback: false, - }, - // Baz was not part of the original serverResultFields, it was injected based on the schema - baz: { - raw: false, - snippet: false, - snippetFallback: false, + expectAction(() => { + ResultSettingsLogic.actions.initializeResultFields( + serverResultFields, + schema, + schemaConflicts + ); + }).toChangeState({ + from: { + dataLoading: true, + saving: true, + }, + ignore: SELECTOR_FIELDS, + to: { + dataLoading: false, + saving: false, + // It converts the passed server result fields to a client results field and stores it + // as resultFields + resultFields: { + foo: { + raw: true, + rawSize: 5, + snippet: false, + snippetFallback: false, + }, + // Baz was not part of the original serverResultFields, it was injected based on the schema + baz: { + raw: false, + snippet: false, + snippetFallback: false, + }, + bar: { + raw: true, + rawSize: 5, + snippet: false, + snippetFallback: false, + }, }, - bar: { - raw: true, - rawSize: 5, - snippet: false, - snippetFallback: false, + // It also saves it as lastSavedResultFields + lastSavedResultFields: { + foo: { + raw: true, + rawSize: 5, + snippet: false, + snippetFallback: false, + }, + // Baz was not part of the original serverResultFields, it was injected based on the schema + baz: { + raw: false, + snippet: false, + snippetFallback: false, + }, + bar: { + raw: true, + rawSize: 5, + snippet: false, + snippetFallback: false, + }, }, + // Stores the provided schema details + schema, + schemaConflicts, }, - // Stores the provided schema details - schema, - schemaConflicts, }); }); - - it('default schema conflicts data if none was provided', () => { - mount(); - - ResultSettingsLogic.actions.initializeResultFields(serverResultFields, schema); - - expect(ResultSettingsLogic.values.schemaConflicts).toEqual({}); - }); }); describe('clearAllFields', () => { it('should remove all settings that have been set for each field', () => { - mount({ - resultFields: { - quuz: { raw: false, snippet: false, snippetFallback: false }, - corge: { raw: true, snippet: false, snippetFallback: true }, + expectAction(() => { + ResultSettingsLogic.actions.clearAllFields(); + }).toChangeState({ + from: { + resultFields: { + quuz: { raw: false, snippet: false, snippetFallback: false }, + corge: { raw: true, snippet: false, snippetFallback: true }, + }, }, - }); - - ResultSettingsLogic.actions.clearAllFields(); - - expect(resultSettingLogicValues()).toEqual({ - ...DEFAULT_VALUES, - resultFields: { - quuz: {}, - corge: {}, + to: { + resultFields: { + quuz: {}, + corge: {}, + }, }, + ignore: SELECTOR_FIELDS, }); }); }); describe('resetAllFields', () => { it('should reset all settings to their default values per field', () => { - mount({ - resultFields: { - quuz: { raw: true, snippet: true, snippetFallback: true }, - corge: { raw: true, snippet: true, snippetFallback: true }, + expectAction(() => { + ResultSettingsLogic.actions.resetAllFields(); + }).toChangeState({ + from: { + resultFields: { + quuz: { raw: true, snippet: true, snippetFallback: true }, + corge: { raw: true, snippet: true, snippetFallback: true }, + }, }, - }); - - ResultSettingsLogic.actions.resetAllFields(); - - expect(resultSettingLogicValues()).toEqual({ - ...DEFAULT_VALUES, - resultFields: { - quuz: { raw: true, snippet: false, snippetFallback: false }, - corge: { raw: true, snippet: false, snippetFallback: false }, + to: { + resultFields: { + quuz: { raw: true, snippet: false, snippetFallback: false }, + corge: { raw: true, snippet: false, snippetFallback: false }, + }, }, + ignore: SELECTOR_FIELDS, }); }); }); @@ -215,52 +212,45 @@ describe('ResultSettingsLogic', () => { }; it('should update settings for an individual field', () => { - mount(initialValues); - - ResultSettingsLogic.actions.updateField('foo', { - raw: true, - snippet: false, - snippetFallback: false, - }); - - expect(resultSettingLogicValues()).toEqual({ - ...DEFAULT_VALUES, - // the settings for foo are updated below for any *ResultFields state in which they appear - resultFields: { - foo: { raw: true, snippet: false, snippetFallback: false }, - bar: { raw: true, snippet: true, snippetFallback: true }, + expectAction(() => { + ResultSettingsLogic.actions.updateField('foo', { + raw: true, + snippet: false, + snippetFallback: false, + }); + }).toChangeState({ + from: initialValues, + to: { + resultFields: { + foo: { raw: true, snippet: false, snippetFallback: false }, + bar: { raw: true, snippet: true, snippetFallback: true }, + }, }, + ignore: SELECTOR_FIELDS, }); }); it('should do nothing if the specified field does not exist', () => { - mount(initialValues); - - ResultSettingsLogic.actions.updateField('baz', { - raw: false, - snippet: false, - snippetFallback: false, - }); - - // 'baz' does not exist in state, so nothing is updated - expect(resultSettingLogicValues()).toEqual({ - ...DEFAULT_VALUES, - ...initialValues, + expectAction(() => { + ResultSettingsLogic.actions.updateField('baz', { + raw: false, + snippet: false, + snippetFallback: false, + }); + }).toChangeState({ + from: initialValues, + to: initialValues, }); }); }); describe('saving', () => { it('sets saving to true', () => { - mount({ - saving: false, - }); - - ResultSettingsLogic.actions.saving(); - - expect(resultSettingLogicValues()).toEqual({ - ...DEFAULT_VALUES, - saving: true, + expectAction(() => { + ResultSettingsLogic.actions.saving(); + }).toChangeState({ + from: { saving: false }, + to: { saving: true }, }); }); }); From d65d56c416b056c6dfc1f49b459ef1bcd11cc28b Mon Sep 17 00:00:00 2001 From: Jason Stoltzfus Date: Fri, 14 May 2021 16:06:14 -0400 Subject: [PATCH 5/6] Fix lint --- .../components/result_settings/result_settings_logic.test.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.test.ts index fc3b4acb91ad90..dde493ee5ac6c8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.test.ts @@ -56,9 +56,6 @@ describe('ResultSettingsLogic', () => { const SELECTOR_FIELDS = Object.keys(SELECTORS); - // Values without selectors - const resultSettingLogicValues = () => omit(ResultSettingsLogic.values, Object.keys(SELECTORS)); - beforeEach(() => { jest.clearAllMocks(); mockEngineValues.engineName = 'test-engine'; From d64c5d629a8eb074da95c4c87b3e3f69a881993d Mon Sep 17 00:00:00 2001 From: Jason Stoltzfus Date: Mon, 17 May 2021 10:39:53 -0400 Subject: [PATCH 6/6] Fix lint --- .../components/result_settings/result_settings_logic.test.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.test.ts index dde493ee5ac6c8..469849116b19f9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.test.ts @@ -9,8 +9,6 @@ import { LogicMounter, mockFlashMessageHelpers, mockHttpValues } from '../../../ import { mockEngineValues } from '../../__mocks__'; -import { omit } from 'lodash'; - import { nextTick } from '@kbn/test/jest'; import { Schema, SchemaConflicts, SchemaType } from '../../../shared/schema/types';