[Unit Testing] Configure react-testing-library queries to use Kibana's data-test-subj instead of default data-testid (#59445)

This commit is contained in:
Anton Dosov 2020-03-10 13:16:58 +01:00 committed by GitHub
parent 03d082c59e
commit 8a3e9c6b32
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 72 additions and 100 deletions

View file

@ -68,7 +68,10 @@ export default {
'<rootDir>/src/dev/jest/setup/polyfills.js',
'<rootDir>/src/dev/jest/setup/enzyme.js',
],
setupFilesAfterEnv: ['<rootDir>/src/dev/jest/setup/mocks.js'],
setupFilesAfterEnv: [
'<rootDir>/src/dev/jest/setup/mocks.js',
'<rootDir>/src/dev/jest/setup/react_testing_library.js',
],
coverageDirectory: '<rootDir>/target/kibana-coverage/jest',
coverageReporters: !!process.env.CODE_COVERAGE ? ['json'] : ['html', 'text'],
moduleFileExtensions: ['js', 'json', 'ts', 'tsx'],

View file

@ -0,0 +1,32 @@
/*
* 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 '@testing-library/jest-dom/extend-expect';
/**
* Have to import "/pure" here to not register afterEach() hook clean up
* in the very beginning. There are couple tests which fail with clean up hook.
* On CI they run before first test which imports '@testing-library/react'
* and registers afterEach hook so the whole suite is passing.
* This have to be fixed as we depend on test order execution
* https://github.com/elastic/kibana/issues/59469
*/
import { configure } from '@testing-library/react/pure';
// instead of default 'data-testid', use kibana's 'data-test-subj'
configure({ testIdAttribute: 'data-test-subj' });

View file

@ -40,6 +40,7 @@ export function createJestConfig({ kibanaDirectory, xPackKibanaDirectory }) {
setupFilesAfterEnv: [
`<rootDir>/dev-tools/jest/setup/setup_test.js`,
`${kibanaDirectory}/src/dev/jest/setup/mocks.js`,
`${kibanaDirectory}/src/dev/jest/setup/react_testing_library.js`,
],
testMatch: ['**/*.test.{js,ts,tsx}'],
transform: {

View file

@ -10,4 +10,3 @@
*/
import 'jest-styled-components';
import '@testing-library/jest-dom/extend-expect';

View file

@ -28,9 +28,9 @@ export function KeyValueTable({
{keyValuePairs.map(({ key, value }) => (
<EuiTableRow key={key}>
<EuiTableRowCell>
<strong data-testid="dot-key">{key}</strong>
<strong data-test-subj="dot-key">{key}</strong>
</EuiTableRowCell>
<EuiTableRowCell data-testid="value">
<EuiTableRowCell data-test-subj="value">
<FormattedValue value={value} />
</EuiTableRowCell>
</EuiTableRow>

View file

@ -256,7 +256,7 @@ export function CustomSelectionTable({
{!singleSelection && (
<EuiCheckbox
id={`${item.id}-checkbox`}
data-testid={`${item.id}-checkbox`}
data-test-subj={`${item.id}-checkbox`}
checked={isItemSelected(item.id)}
onChange={() => toggleItem(item.id)}
type="inList"
@ -265,7 +265,7 @@ export function CustomSelectionTable({
{singleSelection && (
<EuiRadio
id={item.id}
data-testid={`${item.id}-radio-button`}
data-test-subj={`${item.id}-radio-button`}
checked={isItemSelected(item.id)}
onChange={() => toggleItem(item.id)}
disabled={timeseriesOnly && item.isSingleMetricViewerJob === false}

View file

@ -8,7 +8,6 @@ import React from 'react';
import { render } from '@testing-library/react';
import * as CheckPrivilige from '../../../../../privilege/check_privilege';
import { queryByTestSubj } from '../../../../../util/test_utils';
import { DeleteAction } from './action_delete';
@ -21,24 +20,22 @@ jest.mock('../../../../../privilege/check_privilege', () => ({
describe('DeleteAction', () => {
test('When canDeleteDataFrameAnalytics permission is false, button should be disabled.', () => {
const { container } = render(<DeleteAction item={mockAnalyticsListItem} />);
expect(queryByTestSubj(container, 'mlAnalyticsJobDeleteButton')).toHaveAttribute('disabled');
const { getByTestId } = render(<DeleteAction item={mockAnalyticsListItem} />);
expect(getByTestId('mlAnalyticsJobDeleteButton')).toHaveAttribute('disabled');
});
test('When canDeleteDataFrameAnalytics permission is true, button should not be disabled.', () => {
const mock = jest.spyOn(CheckPrivilige, 'checkPermission');
mock.mockImplementation(p => p === 'canDeleteDataFrameAnalytics');
const { container } = render(<DeleteAction item={mockAnalyticsListItem} />);
const { getByTestId } = render(<DeleteAction item={mockAnalyticsListItem} />);
expect(queryByTestSubj(container, 'mlAnalyticsJobDeleteButton')).not.toHaveAttribute(
'disabled'
);
expect(getByTestId('mlAnalyticsJobDeleteButton')).not.toHaveAttribute('disabled');
mock.mockRestore();
});
test('When job is running, delete button should be disabled.', () => {
const { container } = render(
const { getByTestId } = render(
<DeleteAction
item={{
...mockAnalyticsListItem,
@ -47,6 +44,6 @@ describe('DeleteAction', () => {
/>
);
expect(queryByTestSubj(container, 'mlAnalyticsJobDeleteButton')).toHaveAttribute('disabled');
expect(getByTestId('mlAnalyticsJobDeleteButton')).toHaveAttribute('disabled');
});
});

View file

@ -149,7 +149,7 @@ exports[`CalendarForm Renders calendar form 1`] = `
grow={false}
>
<EuiButton
data-testid="ml_save_calendar_button"
data-test-subj="ml_save_calendar_button"
fill={true}
isDisabled={true}
onClick={[MockFunction]}

View file

@ -220,7 +220,7 @@ export const CalendarForm = ({
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton
data-testid="ml_save_calendar_button"
data-test-subj="ml_save_calendar_button"
fill
onClick={isEdit ? onEdit : onCreate}
isDisabled={saveButtonDisabled}

View file

@ -133,7 +133,7 @@ exports[`EventsTable Renders events table with search bar 1`] = `
"filters": Array [],
"toolsRight": Array [
<EuiButton
data-testid="ml_new_event"
data-test-subj="ml_new_event"
iconType="plusInCircle"
isDisabled={false}
onClick={[MockFunction]}
@ -146,7 +146,7 @@ exports[`EventsTable Renders events table with search bar 1`] = `
/>
</EuiButton>,
<EuiButton
data-testid="ml_import_events"
data-test-subj="ml_import_events"
iconType="importAction"
isDisabled={false}
onClick={[MockFunction]}

View file

@ -91,7 +91,7 @@ export const EventsTable = ({
name: '',
render: event => (
<DeleteButton
data-testid="event_delete"
data-test-subj="event_delete"
canDeleteCalendar={canDeleteCalendar}
onClick={() => {
onDeleteClick(event.event_id);
@ -106,7 +106,7 @@ export const EventsTable = ({
<EuiButton
isDisabled={canCreateCalendar === false}
key="ml_new_event"
data-testid="ml_new_event"
data-test-subj="ml_new_event"
size="s"
iconType="plusInCircle"
onClick={showNewEventModal}
@ -119,7 +119,7 @@ export const EventsTable = ({
<EuiButton
isDisabled={canCreateCalendar === false}
key="ml_import_event"
data-testid="ml_import_events"
data-test-subj="ml_import_events"
size="s"
iconType="importAction"
onClick={showImportModal}

View file

@ -51,7 +51,7 @@ describe('ImportModal', () => {
instance.setState(testState);
wrapper.update();
expect(wrapper.state('selectedEvents').length).toBe(2);
const deleteButton = wrapper.find('[data-testid="event_delete"]');
const deleteButton = wrapper.find('[data-test-subj="event_delete"]');
const button = deleteButton.find('EuiButtonEmpty').first();
button.simulate('click');

View file

@ -117,7 +117,7 @@ describe('NewCalendar', () => {
test('Import modal shown on Import Events button click', () => {
const wrapper = mountWithIntl(<NewCalendar {...props} />);
const importButton = wrapper.find('[data-testid="ml_import_events"]');
const importButton = wrapper.find('[data-test-subj="ml_import_events"]');
const button = importButton.find('EuiButton');
button.simulate('click');
@ -127,7 +127,7 @@ describe('NewCalendar', () => {
test('New event modal shown on New event button click', () => {
const wrapper = mountWithIntl(<NewCalendar {...props} />);
const importButton = wrapper.find('[data-testid="ml_new_event"]');
const importButton = wrapper.find('[data-test-subj="ml_new_event"]');
const button = importButton.find('EuiButton');
button.simulate('click');
@ -154,7 +154,7 @@ describe('NewCalendar', () => {
const wrapper = mountWithIntl(<NewCalendar {...noCreateProps} />);
const buttons = wrapper.find('[data-testid="ml_save_calendar_button"]');
const buttons = wrapper.find('[data-test-subj="ml_save_calendar_button"]');
const saveButton = buttons.find('EuiButton');
expect(saveButton.prop('isDisabled')).toBe(true);

View file

@ -1,18 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { queryHelpers } from '@testing-library/react';
/**
* 'react-testing-library provides 'queryByTestId()' to get
* elements with a 'data-testid' attribut. This custom method
* supports querying for the Kibana style 'data-test-subj' attribute.
* @param {HTMLElement} container The wrapping HTML element.
* @param {Matcher} id The 'data-test-subj' id.
* @returns {HTMLElement | null}
*/
export const queryByTestSubj = queryHelpers.queryByAttribute.bind(null, 'data-test-subj');

View file

@ -55,7 +55,6 @@ export const HeaderNavigation: React.FunctionComponent<{ basename: string }> = R
return tabs.map((tab, index) => {
return (
<EuiTab
data-testid={`${tab.id}EndpointTab`}
data-test-subj={`${tab.id}EndpointTab`}
key={index}
href={`${basename}${tab.href}`}

View file

@ -12,7 +12,7 @@ import { AlertIndex } from './index';
import { appStoreFactory } from '../../store';
import { coreMock } from 'src/core/public/mocks';
import { KibanaContextProvider } from '../../../../../../../../src/plugins/kibana_react/public';
import { fireEvent, waitForElement, act } from '@testing-library/react';
import { fireEvent, act } from '@testing-library/react';
import { RouteCapture } from '../route_capture';
import { createMemoryHistory, MemoryHistory } from 'history';
import { Router } from 'react-router-dom';
@ -24,18 +24,6 @@ describe('when on the alerting page', () => {
let history: MemoryHistory<never>;
let store: ReturnType<typeof appStoreFactory>;
/**
* @testing-library/react provides `queryByTestId`, but that uses the data attribute
* 'data-testid' whereas our FTR and EUI's tests all use 'data-test-subj'. While @testing-library/react
* could be configured to use 'data-test-subj', it is not currently configured that way.
*
* This provides an equivalent function to `queryByTestId` but that uses our 'data-test-subj' attribute.
*/
let queryByTestSubjId: (
renderResult: reactTestingLibrary.RenderResult,
testSubjId: string
) => Promise<Element | null>;
beforeEach(async () => {
/**
* Create a 'history' instance that is only in-memory and causes no side effects to the testing environment.
@ -70,17 +58,6 @@ describe('when on the alerting page', () => {
</Provider>
);
};
queryByTestSubjId = async (renderResult, testSubjId) => {
return await waitForElement(
/**
* Use document.body instead of container because EUI renders things like popover out of the DOM heirarchy.
*/
() => document.body.querySelector(`[data-test-subj="${testSubjId}"]`),
{
container: renderResult.container,
}
);
};
});
it('should show a data grid', async () => {
await render().findByTestId('alertListGrid');
@ -147,7 +124,7 @@ describe('when on the alerting page', () => {
/**
* Use our helper function to find the flyout's close button, as it uses a different test ID attribute.
*/
const closeButton = await queryByTestSubjId(renderResult, 'euiFlyoutCloseButton');
const closeButton = await renderResult.findByTestId('euiFlyoutCloseButton');
if (closeButton) {
fireEvent.click(closeButton);
}
@ -169,16 +146,13 @@ describe('when on the alerting page', () => {
describe('when the user changes page size to 10', () => {
beforeEach(async () => {
const renderResult = render();
const paginationButton = await queryByTestSubjId(
renderResult,
'tablePaginationPopoverButton'
);
const paginationButton = await renderResult.findByTestId('tablePaginationPopoverButton');
if (paginationButton) {
act(() => {
fireEvent.click(paginationButton);
});
}
const show10RowsButton = await queryByTestSubjId(renderResult, 'tablePagination-10-rows');
const show10RowsButton = await renderResult.findByTestId('tablePagination-10-rows');
if (show10RowsButton) {
act(() => {
fireEvent.click(show10RowsButton);

View file

@ -142,7 +142,7 @@ export const AlertIndex = memo(() => {
if (columnId === 'alert_type') {
return (
<EuiLink
data-testid="alertTypeCellLink"
data-test-subj="alertTypeCellLink"
onClick={() =>
history.push(urlFromQueryParams({ ...queryParams, selected_alert: row.id }))
}
@ -211,7 +211,7 @@ export const AlertIndex = memo(() => {
return (
<>
{hasSelectedAlert && (
<EuiFlyout data-testid="alertDetailFlyout" size="l" onClose={handleFlyoutClose}>
<EuiFlyout data-test-subj="alertDetailFlyout" size="l" onClose={handleFlyoutClose}>
<EuiFlyoutHeader hasBorder>
<EuiTitle size="m">
<h2>
@ -226,7 +226,7 @@ export const AlertIndex = memo(() => {
</EuiFlyoutBody>
</EuiFlyout>
)}
<EuiPage data-test-subj="alertListPage" data-testid="alertListPage">
<EuiPage data-test-subj="alertListPage">
<EuiPageBody>
<EuiPageContent>
<EuiPageContentHeader>
@ -250,7 +250,6 @@ export const AlertIndex = memo(() => {
renderCellValue={renderCellValue}
pagination={pagination}
data-test-subj="alertListGrid"
data-testid="alertListGrid"
/>
</EuiPageContentBody>
</EuiPageContent>

View file

@ -20,7 +20,7 @@ export const AlertDetailResolver = styled(
const { store } = storeFactory(context);
return (
<div className={className} data-test-subj="alertResolver" data-testid="alertResolver">
<div className={className} data-test-subj="alertResolver">
<Provider store={store}>
<Resolver selectedEvent={selectedEvent} />
</Provider>

View file

@ -22,11 +22,6 @@ describe('when on the managing page', () => {
let history: MemoryHistory<never>;
let store: ReturnType<typeof appStoreFactory>;
let queryByTestSubjId: (
renderResult: reactTestingLibrary.RenderResult,
testSubjId: string
) => Promise<Element | null>;
beforeEach(async () => {
history = createMemoryHistory<never>();
store = appStoreFactory(coreMock.createStart(), true);
@ -43,20 +38,11 @@ describe('when on the managing page', () => {
</Provider>
);
};
queryByTestSubjId = async (renderResult, testSubjId) => {
return await reactTestingLibrary.waitForElement(
() => document.body.querySelector(`[data-test-subj="${testSubjId}"]`),
{
container: renderResult.container,
}
);
};
});
it('should show a table', async () => {
const renderResult = render();
const table = await queryByTestSubjId(renderResult, 'managementListTable');
const table = await renderResult.findByTestId('managementListTable');
expect(table).not.toBeNull();
});
@ -64,7 +50,7 @@ describe('when on the managing page', () => {
it('should not show the flyout', () => {
const renderResult = render();
expect.assertions(1);
return queryByTestSubjId(renderResult, 'managementDetailsFlyout').catch(e => {
return renderResult.findByTestId('managementDetailsFlyout').catch(e => {
expect(e).not.toBeNull();
});
});
@ -89,14 +75,14 @@ describe('when on the managing page', () => {
let renderResult: reactTestingLibrary.RenderResult;
beforeEach(async () => {
renderResult = render();
const detailsLink = await queryByTestSubjId(renderResult, 'hostnameCellLink');
const detailsLink = await renderResult.findByTestId('hostnameCellLink');
if (detailsLink) {
reactTestingLibrary.fireEvent.click(detailsLink);
}
});
it('should show the flyout', () => {
return queryByTestSubjId(renderResult, 'managementDetailsFlyout').then(flyout => {
return renderResult.findByTestId('managementDetailsFlyout').then(flyout => {
expect(flyout).not.toBeNull();
});
});
@ -115,7 +101,7 @@ describe('when on the managing page', () => {
});
it('should show the flyout', () => {
const renderResult = render();
return queryByTestSubjId(renderResult, 'managementDetailsFlyout').then(flyout => {
return renderResult.findByTestId('managementDetailsFlyout').then(flyout => {
expect(flyout).not.toBeNull();
});
});

View file

@ -32,7 +32,7 @@ describe('useCamera on an unpainted element', () => {
const camera = useCamera();
const { ref, onMouseDown } = camera;
projectionMatrix = camera.projectionMatrix;
return <div data-testid={testID} onMouseDown={onMouseDown} ref={ref} />;
return <div data-test-subj={testID} onMouseDown={onMouseDown} ref={ref} />;
};
simulator = sideEffectSimulator();