Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ILM] Refactor component tests #91657

Merged
merged 2 commits into from
Feb 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
* 2.0.
*/

import moment from 'moment-timezone';

import { PolicyFromES } from '../../../common/types';

export const POLICY_NAME = 'my_policy';
Expand Down Expand Up @@ -234,3 +236,32 @@ export const POLICY_WITH_KNOWN_AND_UNKNOWN_FIELDS = ({
},
name: POLICY_NAME,
} as any) as PolicyFromES;

export const getGeneratedPolicies = (): PolicyFromES[] => {
const policy = {
phases: {
hot: {
min_age: '0s',
actions: {
rollover: {
max_size: '1gb',
},
},
},
},
};
const policies: PolicyFromES[] = [];
for (let i = 0; i < 105; i++) {
policies.push({
version: i,
modified_date: moment().subtract(i, 'days').toISOString(),
linkedIndices: i % 2 === 0 ? [`index${i}`] : undefined,
name: `testy${i}`,
policy: {
...policy,
name: `testy${i}`,
},
});
}
return policies;
};
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ import { KibanaContextProvider } from '../../../public/shared_imports';
import { AppServicesContext } from '../../../public/types';
import { createBreadcrumbsMock } from '../../../public/application/services/breadcrumbs.mock';

import { TestSubjects } from '../helpers';
import { POLICY_NAME } from './constants';

type Phases = keyof PolicyPhases;

import { POLICY_NAME } from './constants';
import { TestSubjects } from '../helpers';
window.scrollTo = jest.fn();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should no longer be necessary, if it is then we probably need to remove this from the code because AFAIK this is used to scroll to the first visible error on form submission. It's nice behaviour, but has not been mapped onto the new UI with collapsible settings in phases.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We still use a scroll to 0,0 for when the policy has loaded (edit_policy.tsx#L66). It doesn't affect the tests results though, but I prefer to keep it there for now to avoid unnecessary errors in output.


jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
Expand All @@ -46,14 +48,17 @@ jest.mock('@elastic/eui', () => {
};
});

const testBedConfig: TestBedConfig = {
memoryRouter: {
initialEntries: [`/policies/edit/${POLICY_NAME}`],
componentRoutePath: `/policies/edit/:policyName`,
},
defaultProps: {
getUrlForApp: () => {},
},
const getTestBedConfig = (testBedConfigArgs?: Partial<TestBedConfig>): TestBedConfig => {
return {
memoryRouter: {
initialEntries: [`/policies/edit/${POLICY_NAME}`],
componentRoutePath: `/policies/edit/:policyName`,
},
defaultProps: {
getUrlForApp: () => {},
},
...testBedConfigArgs,
};
};

const breadcrumbService = createBreadcrumbsMock();
Expand All @@ -72,13 +77,22 @@ const MyComponent = ({ appServicesContext, ...rest }: any) => {
);
};

const initTestBed = registerTestBed<TestSubjects>(MyComponent, testBedConfig);
const initTestBed = (arg?: {
appServicesContext?: Partial<AppServicesContext>;
testBedConfig?: Partial<TestBedConfig>;
}) => {
const { testBedConfig: testBedConfigArgs, ...rest } = arg || {};
return registerTestBed<TestSubjects>(MyComponent, getTestBedConfig(testBedConfigArgs))(rest);
};

type SetupReturn = ReturnType<typeof setup>;

export type EditPolicyTestBed = SetupReturn extends Promise<infer U> ? U : SetupReturn;

export const setup = async (arg?: { appServicesContext: Partial<AppServicesContext> }) => {
export const setup = async (arg?: {
appServicesContext?: Partial<AppServicesContext>;
testBedConfig?: Partial<TestBedConfig>;
}) => {
const testBed = await initTestBed(arg);

const { find, component, form, exists } = testBed;
Expand Down Expand Up @@ -169,34 +183,15 @@ export const setup = async (arg?: { appServicesContext: Partial<AppServicesConte

const enable = (phase: Phases) => createFormToggleAction(`enablePhaseSwitch-${phase}`);

const setMinAgeValue = (phase: Phases) => createFormSetValueAction(`${phase}-selectedMinimumAge`);

const setMinAgeUnits = (phase: Phases) =>
createFormSetValueAction(`${phase}-selectedMinimumAgeUnits`);

const setDataAllocation = (phase: Phases) => async (value: DataTierAllocationType) => {
act(() => {
find(`${phase}-dataTierAllocationControls.dataTierSelect`).simulate('click');
});
component.update();
await act(async () => {
switch (value) {
case 'node_roles':
find(`${phase}-dataTierAllocationControls.defaultDataAllocationOption`).simulate('click');
break;
case 'node_attrs':
find(`${phase}-dataTierAllocationControls.customDataAllocationOption`).simulate('click');
break;
default:
find(`${phase}-dataTierAllocationControls.noneDataAllocationOption`).simulate('click');
}
});
component.update();
const createMinAgeActions = (phase: Phases) => {
return {
hasMinAgeInput: () => exists(`${phase}-selectedMinimumAge`),
setMinAgeValue: createFormSetValueAction(`${phase}-selectedMinimumAge`),
setMinAgeUnits: createFormSetValueAction(`${phase}-selectedMinimumAgeUnits`),
hasRolloverTipOnMinAge: () => exists(`${phase}-rolloverMinAgeInputIconTip`),
};
};

const setSelectedNodeAttribute = (phase: Phases) =>
createFormSetValueAction(`${phase}-selectedNodeAttrs`);

const setReplicas = (phase: Phases) => async (value: string) => {
if (!exists(`${phase}-selectedReplicaCount`)) {
await createFormToggleAction(`${phase}-setReplicasSwitch`)(true);
Expand All @@ -216,8 +211,12 @@ export const setup = async (arg?: { appServicesContext: Partial<AppServicesConte
const setFreeze = createFormToggleAction('freezeSwitch');
const freezeExists = () => exists('freezeSwitch');

const setReadonly = (phase: Phases) => async (value: boolean) => {
await createFormToggleAction(`${phase}-readonlySwitch`)(value);
const createReadonlyActions = (phase: Phases) => {
const toggleSelector = `${phase}-readonlySwitch`;
return {
readonlyExists: () => exists(toggleSelector),
toggleReadonly: createFormToggleAction(toggleSelector),
};
};

const createSearchableSnapshotActions = (phase: Phases) => {
Expand Down Expand Up @@ -271,17 +270,93 @@ export const setup = async (arg?: { appServicesContext: Partial<AppServicesConte
};
};

const hasRolloverTipOnMinAge = (phase: Phases) => (): boolean =>
exists(`${phase}-rolloverMinAgeInputIconTip`);
const hasRolloverSettingRequiredCallout = (): boolean => exists('rolloverSettingsRequired');

const createNodeAllocationActions = (phase: Phases) => {
const controlsSelector = `${phase}-dataTierAllocationControls`;
const dataTierSelector = `${controlsSelector}.dataTierSelect`;
const nodeAttrsSelector = `${phase}-selectedNodeAttrs`;

return {
hasDataTierAllocationControls: () => exists(controlsSelector),
openNodeAttributesSection: async () => {
await act(async () => {
find(dataTierSelector).simulate('click');
});
component.update();
},
hasNodeAttributesSelect: (): boolean => exists(nodeAttrsSelector),
getNodeAttributesSelectOptions: () => find(nodeAttrsSelector).find('option'),
setDataAllocation: async (value: DataTierAllocationType) => {
act(() => {
find(dataTierSelector).simulate('click');
});
component.update();
await act(async () => {
switch (value) {
case 'node_roles':
find(`${controlsSelector}.defaultDataAllocationOption`).simulate('click');
break;
case 'node_attrs':
find(`${controlsSelector}.customDataAllocationOption`).simulate('click');
break;
default:
find(`${controlsSelector}.noneDataAllocationOption`).simulate('click');
}
});
component.update();
},
setSelectedNodeAttribute: createFormSetValueAction(nodeAttrsSelector),
hasNoNodeAttrsWarning: () => exists('noNodeAttributesWarning'),
hasDefaultAllocationWarning: () => exists('defaultAllocationWarning'),
hasDefaultAllocationNotice: () => exists('defaultAllocationNotice'),
hasNodeDetailsFlyout: () => exists(`${phase}-viewNodeDetailsFlyoutButton`),
openNodeDetailsFlyout: async () => {
await act(async () => {
find(`${phase}-viewNodeDetailsFlyoutButton`).simulate('click');
});
component.update();
},
};
};

const expectErrorMessages = (expectedMessages: string[]) => {
const errorMessages = component.find('.euiFormErrorText');
expect(errorMessages.length).toBe(expectedMessages.length);
expectedMessages.forEach((expectedErrorMessage) => {
let foundErrorMessage;
for (let i = 0; i < errorMessages.length; i++) {
if (errorMessages.at(i).text() === expectedErrorMessage) {
foundErrorMessage = true;
}
}
expect(foundErrorMessage).toBe(true);
});
};

/*
* For new we rely on a setTimeout to ensure that error messages have time to populate
* the form object before we look at the form object. See:
* x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/form_errors_context.tsx
* for where this logic lives.
*/
const runTimers = () => {
act(() => {
jest.runAllTimers();
});
component.update();
};

return {
...testBed,
runTimers,
actions: {
saveAsNewPolicy: createFormToggleAction('saveAsNewSwitch'),
setPolicyName: createFormSetValueAction('policyNameField'),
setWaitForSnapshotPolicy,
savePolicy,
hasGlobalErrorCallout: () => exists('policyFormErrorsCallout'),
expectErrorMessages,
timeline: {
hasHotPhase: () => exists('ilmTimelineHotPhase'),
hasWarmPhase: () => exists('ilmTimelineWarmPhase'),
Expand All @@ -294,46 +369,40 @@ export const setup = async (arg?: { appServicesContext: Partial<AppServicesConte
setMaxAge,
toggleRollover,
toggleDefaultRollover,
hasRolloverSettingRequiredCallout,
hasErrorIndicator: () => exists('phaseErrorIndicator-hot'),
...createForceMergeActions('hot'),
...createIndexPriorityActions('hot'),
...createShrinkActions('hot'),
setReadonly: setReadonly('hot'),
...createReadonlyActions('hot'),
...createSearchableSnapshotActions('hot'),
},
warm: {
enable: enable('warm'),
setMinAgeValue: setMinAgeValue('warm'),
setMinAgeUnits: setMinAgeUnits('warm'),
setDataAllocation: setDataAllocation('warm'),
setSelectedNodeAttribute: setSelectedNodeAttribute('warm'),
...createMinAgeActions('warm'),
setReplicas: setReplicas('warm'),
hasErrorIndicator: () => exists('phaseErrorIndicator-warm'),
hasRolloverTipOnMinAge: hasRolloverTipOnMinAge('warm'),
...createShrinkActions('warm'),
...createForceMergeActions('warm'),
setReadonly: setReadonly('warm'),
...createReadonlyActions('warm'),
...createIndexPriorityActions('warm'),
...createNodeAllocationActions('warm'),
},
cold: {
enable: enable('cold'),
setMinAgeValue: setMinAgeValue('cold'),
setMinAgeUnits: setMinAgeUnits('cold'),
setDataAllocation: setDataAllocation('cold'),
setSelectedNodeAttribute: setSelectedNodeAttribute('cold'),
...createMinAgeActions('cold'),
setReplicas: setReplicas('cold'),
setFreeze,
freezeExists,
hasErrorIndicator: () => exists('phaseErrorIndicator-cold'),
hasRolloverTipOnMinAge: hasRolloverTipOnMinAge('cold'),
...createIndexPriorityActions('cold'),
...createSearchableSnapshotActions('cold'),
...createNodeAllocationActions('cold'),
},
delete: {
isShown: () => exists('delete-phaseContent'),
...createToggleDeletePhaseActions(),
hasRolloverTipOnMinAge: hasRolloverTipOnMinAge('delete'),
setMinAgeValue: setMinAgeValue('delete'),
setMinAgeUnits: setMinAgeUnits('delete'),
...createMinAgeActions('delete'),
},
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ import {
getDefaultHotPhasePolicy,
} from './constants';

window.scrollTo = jest.fn();

describe('<EditPolicy />', () => {
let testBed: EditPolicyTestBed;
const { server, httpRequestsMockHelpers } = setupEnvironment();
Expand Down Expand Up @@ -127,7 +125,7 @@ describe('<EditPolicy />', () => {
await actions.hot.setBestCompression(true);
await actions.hot.toggleShrink(true);
await actions.hot.setShrink('2');
await actions.hot.setReadonly(true);
await actions.hot.toggleReadonly(true);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've realised that these toggle/set functions do not actually set the boolean value, they just toggle whatever the toggle state is relative to what it was before. Not something to action, just observing since I introduced the boolean arg here.

Might just need some slight investigation to fix, @sebelga have you encountered this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pointing this out @jloleysens! It's worth investigating for sure, I'll put it in an issue.

await actions.hot.toggleIndexPriority(true);
await actions.hot.setIndexPriority('123');

Expand Down Expand Up @@ -271,7 +269,7 @@ describe('<EditPolicy />', () => {
await actions.warm.toggleForceMerge(true);
await actions.warm.setForcemergeSegmentsCount('123');
await actions.warm.setBestCompression(true);
await actions.warm.setReadonly(true);
await actions.warm.toggleReadonly(true);
await actions.warm.setIndexPriority('123');
await actions.savePolicy();
const latestRequest = server.requests[server.requests.length - 1];
Expand Down Expand Up @@ -918,13 +916,15 @@ describe('<EditPolicy />', () => {
});

describe('policy error notifications', () => {
let runTimers: () => void;
beforeAll(() => {
jest.useFakeTimers();
});

afterAll(() => {
jest.useRealTimers();
});

beforeEach(async () => {
httpRequestsMockHelpers.setLoadPolicies([getDefaultHotPhasePolicy('my_policy')]);
httpRequestsMockHelpers.setListNodes({
Expand All @@ -940,19 +940,9 @@ describe('<EditPolicy />', () => {

const { component } = testBed;
component.update();
});

// For new we rely on a setTimeout to ensure that error messages have time to populate
// the form object before we look at the form object. See:
// x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/form_errors_context.tsx
// for where this logic lives.
const runTimers = () => {
const { component } = testBed;
act(() => {
jest.runAllTimers();
});
component.update();
};
({ runTimers } = testBed);
});

test('shows phase error indicators correctly', async () => {
// This test simulates a user configuring a policy phase by phase. The flow is the following:
Expand Down
Loading