Add snapshotComponent test service for Jest. (#14226)

* Reorganize jest directory and add a setup file which polyfills window.requestAnimationFrame.
* Add takeMountedSnapshot test service. Update KuiCodeEditor test to use it.
- Refactor tests to use a test subject selector to locate the hint element.
- Refine tests to leverage snapshots instead of DOM assertions.
* Update dashboard_panel test to use takeMountedSnapshot.
- Update snapshot.
- Update Jest config to make takeMountedSnapshot available to Kibana src.
This commit is contained in:
CJ Cenizal 2017-10-02 08:38:39 -07:00 committed by GitHub
parent ba7187225e
commit 34250a81e8
11 changed files with 286 additions and 205 deletions

View file

@ -1,202 +1,91 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`DashboardPanel matches snapshot 1`] = `
<DashboardPanel
dashboardViewMode="edit"
getContainerApi={[Function]}
getEmbeddableHandler={[Function]}
isExpanded={false}
isFullScreenMode={false}
onDeletePanel={[Function]}
onToggleExpanded={[Function]}
panel={
Object {
"gridData": Object {
"h": 6,
"i": 1,
"w": 6,
"x": 0,
"y": 0,
},
"id": "foo1",
"panelIndex": "1",
"type": "visualization",
}
}
<div
class="dashboard-panel"
>
<div
className="dashboard-panel"
onBlur={[Function]}
onFocus={[Function]}
class="panel panel-default panel--edit-mode"
data-test-subj="dashboardPanel"
>
<div
className="panel panel-default panel--edit-mode"
data-test-subj="dashboardPanel"
class="panel-heading"
>
<PanelHeader
isExpanded={false}
isViewOnlyMode={false}
onDeletePanel={[Function]}
onEditPanel={[Function]}
onToggleExpand={[Function]}
<span
aria-label="Dashboard panel: undefined"
class="panel-title"
data-test-subj="dashboardPanelTitle"
/>
<div
class="kuiMicroButtonGroup"
>
<div
className="panel-heading"
class="kuiPopover kuiPopover--anchorRight dashboardPanelPopOver"
>
<span
aria-label="Dashboard panel: undefined"
className="panel-title"
data-test-subj="dashboardPanelTitle"
aria-label="Click for more panel options"
class="kuiButton__icon kuiIcon panel-dropdown fa fa-caret-down"
data-test-subj="dashboardPanelToggleMenuIcon"
role="button"
tabindex="0"
/>
<div
className="kuiMicroButtonGroup"
class="kuiPopover__body"
>
<PanelOptionsMenu
isExpanded={false}
onDeletePanel={[Function]}
onEditPanel={[Function]}
onToggleExpandPanel={[Function]}
<ul
class="kuiMenu"
>
<KuiPopover
anchorPosition="right"
button={
<KuiKeyboardAccessible>
<span
aria-label="Click for more panel options"
className="kuiButton__icon kuiIcon panel-dropdown fa fa-caret-down"
data-test-subj="dashboardPanelToggleMenuIcon"
onClick={[Function]}
/>
</KuiKeyboardAccessible>
}
className="dashboardPanelPopOver"
closePopover={[Function]}
isOpen={false}
<li
class="kuiMenuItem dashboardPanelMenuItem"
data-test-subj="dashboardPanelEditLink"
>
<KuiOutsideClickDetector
onOutsideClick={[Function]}
<span
aria-hidden="true"
class="kuiButton__icon kuiIcon fa-edit"
/>
<p
class="kuiText"
>
<div
className="kuiPopover kuiPopover--anchorRight dashboardPanelPopOver"
>
<KuiKeyboardAccessible>
<span
aria-label="Click for more panel options"
className="kuiButton__icon kuiIcon panel-dropdown fa fa-caret-down"
data-test-subj="dashboardPanelToggleMenuIcon"
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
role="button"
tabIndex="0"
/>
</KuiKeyboardAccessible>
<div
className="kuiPopover__body"
>
<KuiMenu>
<ul
className="kuiMenu"
>
<PanelMenuItem
data-test-subj="dashboardPanelEditLink"
iconClass="fa-edit"
label="Edit Visualization"
onClick={[Function]}
>
<KuiMenuItem
className="dashboardPanelMenuItem"
data-test-subj="dashboardPanelEditLink"
onClick={[Function]}
>
<li
className="kuiMenuItem dashboardPanelMenuItem"
data-test-subj="dashboardPanelEditLink"
onClick={[Function]}
>
<span
aria-hidden="true"
className="kuiButton__icon kuiIcon fa-edit"
/>
<p
className="kuiText"
>
Edit Visualization
</p>
</li>
</KuiMenuItem>
</PanelMenuItem>
<PanelMenuItem
data-test-subj="dashboardPanelExpandIcon"
iconClass="fa-expand"
label="Full screen"
onClick={[Function]}
>
<KuiMenuItem
className="dashboardPanelMenuItem"
data-test-subj="dashboardPanelExpandIcon"
onClick={[Function]}
>
<li
className="kuiMenuItem dashboardPanelMenuItem"
data-test-subj="dashboardPanelExpandIcon"
onClick={[Function]}
>
<span
aria-hidden="true"
className="kuiButton__icon kuiIcon fa-expand"
/>
<p
className="kuiText"
>
Full screen
</p>
</li>
</KuiMenuItem>
</PanelMenuItem>
<PanelMenuItem
data-test-subj="dashboardPanelRemoveIcon"
iconClass="fa-trash"
label="Delete from dashboard"
onClick={[Function]}
>
<KuiMenuItem
className="dashboardPanelMenuItem"
data-test-subj="dashboardPanelRemoveIcon"
onClick={[Function]}
>
<li
className="kuiMenuItem dashboardPanelMenuItem"
data-test-subj="dashboardPanelRemoveIcon"
onClick={[Function]}
>
<span
aria-hidden="true"
className="kuiButton__icon kuiIcon fa-trash"
/>
<p
className="kuiText"
>
Delete from dashboard
</p>
</li>
</KuiMenuItem>
</PanelMenuItem>
</ul>
</KuiMenu>
</div>
</div>
</KuiOutsideClickDetector>
</KuiPopover>
</PanelOptionsMenu>
Edit Visualization
</p>
</li>
<li
class="kuiMenuItem dashboardPanelMenuItem"
data-test-subj="dashboardPanelExpandIcon"
>
<span
aria-hidden="true"
class="kuiButton__icon kuiIcon fa-expand"
/>
<p
class="kuiText"
>
Full screen
</p>
</li>
<li
class="kuiMenuItem dashboardPanelMenuItem"
data-test-subj="dashboardPanelRemoveIcon"
>
<span
aria-hidden="true"
class="kuiButton__icon kuiIcon fa-trash"
/>
<p
class="kuiText"
>
Delete from dashboard
</p>
</li>
</ul>
</div>
</div>
</PanelHeader>
<div
className="panel-content"
id="embeddedPanel"
/>
</div>
</div>
<div
class="panel-content"
id="embeddedPanel"
/>
</div>
</DashboardPanel>
</div>
`;

View file

@ -5,6 +5,10 @@ import { DashboardViewMode } from '../dashboard_view_mode';
import { DashboardPanel } from './dashboard_panel';
import { PanelError } from '../panel/panel_error';
import {
takeMountedSnapshot,
} from 'ui_framework/src/test';
const containerApiMock = {
addFilter: () => {},
getAppState: () => {},
@ -40,7 +44,7 @@ function getProps(props = {}) {
test('DashboardPanel matches snapshot', () => {
const component = mount(<DashboardPanel {...getProps()} />);
expect(component).toMatchSnapshot();
expect(takeMountedSnapshot(component)).toMatchSnapshot();
});
test('and calls render', () => {

View file

@ -15,10 +15,14 @@
"moduleNameMapper": {
"^ui_framework/components": "<rootDir>/ui_framework/components",
"^ui_framework/services": "<rootDir>/ui_framework/services",
"^ui_framework/src/test": "<rootDir>/ui_framework/src/test",
"^ui/(.*)": "<rootDir>/src/ui/public/$1",
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/src/jest/file_mock.js",
"\\.(css|less|scss)$": "<rootDir>/src/jest/style_mock.js"
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/src/jest/mocks/file_mock.js",
"\\.(css|less|scss)$": "<rootDir>/src/jest/mocks/style_mock.js"
},
"setupFiles": [
"<rootDir>/src/jest/setup/request_animation_frame_polyfill.js"
],
"coverageDirectory": "<rootDir>/target/jest-coverage",
"coverageReporters": [
"html"

View file

@ -0,0 +1,18 @@
// requestAnimationFrame isn't available in node so we need to polyfill it.
// Borrowed from https://gist.github.com/paulirish/1579671.
window.requestAnimationFrame = (() => {
let clock = Date.now();
return callback => {
const currentTime = Date.now();
if (currentTime - clock > 16) {
clock = currentTime;
callback(currentTime);
} else {
setTimeout(() => {
window.requestAnimationFrame(callback);
}, 0);
}
};
})();

View file

@ -1,3 +1,160 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`KuiCodeEditor is rendered 1`] = `"<div class=\\"kuiCodeEditorWrapper\\"><div class=\\"kuiCodeEditorKeyboardHint\\" id=\\"42\\" tabindex=\\"0\\" role=\\"button\\"><p class=\\"kuiText kuiVerticalRhythmSmall\\">Press Enter to start editing.</p><p class=\\"kuiText kuiVerticalRhythmSmall\\">When youre done, press Escape to stop editing.</p></div><div id=\\"brace-editor\\" style=\\"width: 500px; height: 500px;\\" class=\\" ace_editor ace-tm testClass1 testClass2\\"><textarea class=\\"ace_text-input\\" wrap=\\"off\\" autocorrect=\\"off\\" autocapitalize=\\"off\\" spellcheck=\\"false\\" style=\\"opacity: 0;\\" tabindex=\\"-1\\"></textarea><div class=\\"ace_gutter\\"><div class=\\"ace_layer ace_gutter-layer ace_folding-enabled\\"></div><div class=\\"ace_gutter-active-line\\"></div></div><div class=\\"ace_scroller\\"><div class=\\"ace_content\\"><div class=\\"ace_layer ace_print-margin-layer\\"><div class=\\"ace_print-margin\\" style=\\"left: 4px; visibility: visible;\\"></div></div><div class=\\"ace_layer ace_marker-layer\\"></div><div class=\\"ace_layer ace_text-layer\\" style=\\"padding: 0px 4px;\\"></div><div class=\\"ace_layer ace_marker-layer\\"></div><div class=\\"ace_layer ace_cursor-layer ace_hidden-cursors\\"><div class=\\"ace_cursor\\"></div></div></div></div><div class=\\"ace_scrollbar ace_scrollbar-v\\" style=\\"display: none; width: 20px;\\"><div class=\\"ace_scrollbar-inner\\" style=\\"width: 20px;\\"></div></div><div class=\\"ace_scrollbar ace_scrollbar-h\\" style=\\"display: none; height: 20px;\\"><div class=\\"ace_scrollbar-inner\\" style=\\"height: 20px;\\"></div></div><div style=\\"height: auto; width: auto; top: 0px; left: 0px; visibility: hidden; position: absolute; white-space: pre; overflow: hidden;\\"><div style=\\"height: auto; width: auto; top: 0px; left: 0px; visibility: hidden; position: absolute; white-space: pre; overflow: visible;\\"></div><div style=\\"height: auto; width: auto; top: 0px; left: 0px; visibility: hidden; position: absolute; white-space: pre; overflow: visible;\\">XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX</div></div></div></div>"`;
exports[`KuiCodeEditor hint element should be disabled when the ui ace box gains focus 1`] = `
<div
className="kuiCodeEditorKeyboardHint kuiCodeEditorKeyboardHint-isInactive"
data-test-subj="codeEditorHint"
id={42}
onClick={[Function]}
onKeyDown={[Function]}
role="button"
tabIndex="0"
>
<p
className="kuiText kuiVerticalRhythmSmall"
>
Press Enter to start editing.
</p>
<p
className="kuiText kuiVerticalRhythmSmall"
>
When youre done, press Escape to stop editing.
</p>
</div>
`;
exports[`KuiCodeEditor hint element should be enabled when the ui ace box loses focus 1`] = `
<div
className="kuiCodeEditorKeyboardHint"
data-test-subj="codeEditorHint"
id={42}
onClick={[Function]}
onKeyDown={[Function]}
role="button"
tabIndex="0"
>
<p
className="kuiText kuiVerticalRhythmSmall"
>
Press Enter to start editing.
</p>
<p
className="kuiText kuiVerticalRhythmSmall"
>
When youre done, press Escape to stop editing.
</p>
</div>
`;
exports[`KuiCodeEditor is rendered 1`] = `
<div
class="kuiCodeEditorWrapper"
>
<div
class="kuiCodeEditorKeyboardHint"
data-test-subj="codeEditorHint"
id="42"
role="button"
tabindex="0"
>
<p
class="kuiText kuiVerticalRhythmSmall"
>
Press Enter to start editing.
</p>
<p
class="kuiText kuiVerticalRhythmSmall"
>
When youre done, press Escape to stop editing.
</p>
</div>
<div
class=" ace_editor ace-tm testClass1 testClass2"
id="brace-editor"
style="width: 500px; height: 500px;"
>
<textarea
autocapitalize="off"
autocorrect="off"
class="ace_text-input"
spellcheck="false"
style="opacity: 0;"
tabindex="-1"
wrap="off"
/>
<div
class="ace_gutter"
>
<div
class="ace_layer ace_gutter-layer ace_folding-enabled"
/>
<div
class="ace_gutter-active-line"
/>
</div>
<div
class="ace_scroller"
>
<div
class="ace_content"
>
<div
class="ace_layer ace_print-margin-layer"
>
<div
class="ace_print-margin"
style="left: 4px; visibility: visible;"
/>
</div>
<div
class="ace_layer ace_marker-layer"
/>
<div
class="ace_layer ace_text-layer"
style="padding: 0px 4px;"
/>
<div
class="ace_layer ace_marker-layer"
/>
<div
class="ace_layer ace_cursor-layer ace_hidden-cursors"
>
<div
class="ace_cursor"
/>
</div>
</div>
</div>
<div
class="ace_scrollbar ace_scrollbar-v"
style="display: none; width: 20px;"
>
<div
class="ace_scrollbar-inner"
style="width: 20px;"
/>
</div>
<div
class="ace_scrollbar ace_scrollbar-h"
style="display: none; height: 20px;"
>
<div
class="ace_scrollbar-inner"
style="height: 20px;"
/>
</div>
<div
style="height: auto; width: auto; top: 0px; left: 0px; visibility: hidden; position: absolute; white-space: pre; overflow: hidden;"
>
<div
style="height: auto; width: auto; top: 0px; left: 0px; visibility: hidden; position: absolute; white-space: pre; overflow: visible;"
/>
<div
style="height: auto; width: auto; top: 0px; left: 0px; visibility: hidden; position: absolute; white-space: pre; overflow: visible;"
>
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
</div>
</div>
</div>
</div>
`;

View file

@ -71,6 +71,7 @@ export class KuiCodeEditor extends Component {
role="button"
onClick={this.startEditing}
onKeyDown={this.onKeyDownHint}
data-test-subj="codeEditorHint"
>
<p className="kuiText kuiVerticalRhythmSmall">
Press Enter to start editing.

View file

@ -3,7 +3,10 @@ import sinon from 'sinon';
import { mount } from 'enzyme';
import { KuiCodeEditor } from './code_editor';
import { keyCodes } from '../../services';
import { requiredProps } from '../../test/required_props';
import {
requiredProps,
takeMountedSnapshot,
} from '../../test';
// Mock the htmlIdGenerator to generate predictable ids for snapshot tests
jest.mock('../../services/accessibility/html_id_generator', () => ({
@ -11,7 +14,6 @@ jest.mock('../../services/accessibility/html_id_generator', () => ({
}));
describe('KuiCodeEditor', () => {
let element;
beforeEach(() => {
@ -19,38 +21,30 @@ describe('KuiCodeEditor', () => {
});
test('is rendered', () => {
const component = <KuiCodeEditor {...requiredProps}/>;
expect(mount(component).html()).toMatchSnapshot();
const component = mount(<KuiCodeEditor {...requiredProps}/>);
expect(takeMountedSnapshot(component)).toMatchSnapshot();
});
describe('hint element', () => {
test('should exist', () => {
expect(element.find('.kuiCodeEditorKeyboardHint').exists()).toBe(true);
});
test('should be tabable', () => {
expect(element.find('.kuiCodeEditorKeyboardHint').prop('tabIndex')).toBe('0');
expect(element.find('[data-test-subj="codeEditorHint"]').prop('tabIndex')).toBe('0');
});
test('should vanish when hit enter on it', () => {
const hint = element.find('.kuiCodeEditorKeyboardHint');
test('should be disabled when the ui ace box gains focus', () => {
const hint = element.find('[data-test-subj="codeEditorHint"]');
hint.simulate('keydown', { keyCode: keyCodes.ENTER });
expect(hint.hasClass('kuiCodeEditorKeyboardHint-isInactive')).toBe(true);
expect(hint).toMatchSnapshot();
});
test('should be enabled after bluring the ui ace box', () => {
const hint = element.find('.kuiCodeEditorKeyboardHint');
test('should be enabled when the ui ace box loses focus', () => {
const hint = element.find('[data-test-subj="codeEditorHint"]');
hint.simulate('keydown', { keyCode: keyCodes.ENTER });
expect(hint.hasClass('kuiCodeEditorKeyboardHint-isInactive')).toBe(true);
element.instance().onBlurAce();
expect(hint.hasClass('kuiCodeEditorKeyboardHint-isInactive')).toBe(false);
expect(hint).toMatchSnapshot();
});
});
describe('interaction', () => {
test('bluring the ace textbox should call a passed onBlur prop', () => {
const blurSpy = sinon.spy();
const el = mount(<KuiCodeEditor onBlur={blurSpy}/>);
@ -64,9 +58,9 @@ describe('KuiCodeEditor', () => {
stopPropagation: () => {},
keyCode: keyCodes.ESCAPE
});
expect(element.find('.kuiCodeEditorKeyboardHint').matchesElement(document.activeElement)).toBe(true);
expect(
element.find('[data-test-subj="codeEditorHint"]').matchesElement(document.activeElement)
).toBe(true);
});
});
});

View file

@ -0,0 +1,2 @@
export { requiredProps } from './required_props';
export { takeMountedSnapshot } from './take_mounted_snapshot';

View file

@ -0,0 +1,12 @@
/**
* Use this function to generate a Jest snapshot of components that have been fully rendered
* using Enzyme's `mount` method. Typically, a mounted component will result in a snapshot
* containing both React components and HTML elements. This function removes the React components,
* leaving only HTML elements in the snapshot.
*/
export const takeMountedSnapshot = mountedComponent => {
const html = mountedComponent.html();
const template = document.createElement('template');
template.innerHTML = html;
return template.content.firstChild;
};