feat(tests): add test coverage for the main modal

This commit is contained in:
Łukasz Mierzwa
2018-08-23 14:32:23 +01:00
parent 01e2e2251a
commit bd99ef8b51
5 changed files with 1260 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
import React from "react";
import renderer from "react-test-renderer";
import { Help } from "./Help";
describe("<Help />", () => {
it("matches snapshot", () => {
const tree = renderer.create(<Help />).toJSON();
expect(tree).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,80 @@
import React from "react";
import ReactDOM from "react-dom";
import renderer from "react-test-renderer";
import { AlertStore } from "Stores/AlertStore";
import { Settings } from "Stores/Settings";
import { MainModalContent } from "./MainModalContent";
let alertStore;
let settingsStore;
const onHide = jest.fn();
beforeAll(() => {
// modal renders into document.body using portals, but that isn't working
// out of the box with react-test-renderer, a workaround is needed based on
// https://github.com/facebook/react/issues/11565#issuecomment-380143358
ReactDOM.createPortal = jest.fn((element, node) => {
return element;
});
});
beforeEach(() => {
alertStore = new AlertStore([]);
settingsStore = new Settings();
onHide.mockClear();
});
afterEach(() => {
// https://github.com/facebook/react/issues/11565#issuecomment-380143358
ReactDOM.createPortal.mockClear();
});
const FakeModal = () => {
return renderer.create(
<MainModalContent
alertStore={alertStore}
settingsStore={settingsStore}
onHide={onHide}
/>
);
};
const ValidateSetTab = (title, callArg) => {
const component = FakeModal();
const instance = component.getInstance();
const setTabSpy = jest.spyOn(instance.tab, "setTab");
const helpTab = component.root.findByProps({ title: title });
helpTab.props.onClick();
expect(setTabSpy).toHaveBeenCalledWith(callArg);
};
describe("<MainModalContent />", () => {
it("matches snapshot", () => {
const tree = FakeModal().toJSON();
expect(tree).toMatchSnapshot();
});
it("shows 'Configuration' tab by default", () => {
const component = FakeModal();
const tabs = component.root.findAll(testInstance => {
if (!testInstance.props.className) return false;
const classNames = testInstance.props.className.split(" ");
return classNames.includes("nav-link") && classNames.includes("active");
});
expect(tabs).toHaveLength(1);
expect(tabs[0].children).toContain("Configuration");
});
// modal makes it tricky to verify re-rendered content, so only check if we
// update the store for now
it("calls setTab('configuration') after clicking on the 'Configuration' tab", () => {
ValidateSetTab("Configuration", "configuration");
});
it("calls setTab('help') after clicking on the 'Help' tab", () => {
ValidateSetTab("Help", "help");
});
});

View File

@@ -0,0 +1,879 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Help /> matches snapshot 1`] = `
<div>
<h4
className="text-center"
>
Filter operators
</h4>
<dl>
<dt>
<kbd>
=
</kbd>
Exact match
</dt>
<dd
className="mb-3"
>
<div>
Example:
<code>
key
=
value
</code>
</div>
<div>
True if compared alert attribute value is equal to
<code>
value
</code>
.
</div>
</dd>
<dt>
<kbd>
!=
</kbd>
Negative match
</dt>
<dd
className="mb-3"
>
<div>
Example:
<code>
key
!=
value
</code>
</div>
<div>
True if compared alert attribute is missing or have a value that is not equal to
<code>
value
</code>
.
</div>
</dd>
<dt>
<kbd>
=~
</kbd>
Regular expression match
</dt>
<dd
className="mb-3"
>
<div>
Example:
<code>
key
=~
value
</code>
</div>
<div>
True if compared alert attribute value matches
<code>
value
</code>
regex.
</div>
</dd>
<dt>
<kbd>
!~
</kbd>
Negative regular expression match
</dt>
<dd
className="mb-3"
>
<div>
Example:
<code>
key
!~
value
</code>
</div>
<div>
False if compared alert attribute value matches
<code>
value
</code>
regex.
</div>
</dd>
<dt>
<kbd>
&gt;
</kbd>
Greater than match
</dt>
<dd
className="mb-3"
>
<div>
Example:
<code>
key
&gt;
value
</code>
</div>
<div>
True if compared alert attribute value is greater than
<code>
value
</code>
.
</div>
</dd>
<dt>
<kbd>
&lt;
</kbd>
Less than match
</dt>
<dd
className="mb-3"
>
<div>
Example:
<code>
key
&lt;
value
</code>
</div>
<div>
True if compared alert attribue value is less than
<code>
value
</code>
.
</div>
</dd>
</dl>
<div
className="mt-5"
>
<h4
className="text-center"
>
Filtering using alert labels
</h4>
<dl>
<dt>
Match alerts based on any label
</dt>
<dd
className="mb-5"
>
<div>
Supported operators:
<kbd
className="mr-1"
>
=
</kbd>
<kbd
className="mr-1"
>
!=
</kbd>
<kbd
className="mr-1"
>
=~
</kbd>
<kbd
className="mr-1"
>
!~
</kbd>
<kbd
className="mr-1"
>
&gt;
</kbd>
<kbd
className="mr-1"
>
&lt;
</kbd>
</div>
<div>
Examples:
</div>
<ul>
<li>
<div>
<span
className="badge badge-info"
>
alertname=UnableToPing
</span>
</div>
<div>
Match alerts with label
<code>
alertname
</code>
equal to
<code>
UnableToPing
</code>
.
</div>
</li>
<li>
<div>
<span
className="badge badge-info"
>
hostname=localhost
</span>
</div>
<div>
Match alerts with label
<code>
hostname
</code>
equal to
<code>
localhost
</code>
.
</div>
</li>
<li>
<div>
<span
className="badge badge-info"
>
service!=apache3
</span>
</div>
<div>
Match alerts with label
<code>
service
</code>
missing or not equal to
<code>
apache3
</code>
.
</div>
</li>
<li>
<div>
<span
className="badge badge-info"
>
service=~apache
</span>
</div>
<div>
Match alerts with label
<code>
service
</code>
matching regular expression
<code>
/.*apache.*/
</code>
.
</div>
</li>
<li>
<div>
<span
className="badge badge-info"
>
service=~apache[1-3]
</span>
</div>
<div>
Match alerts with label
<code>
service
</code>
matching regular expression
<code>
/.*apache[1-3].*/
</code>
.
</div>
</li>
<li>
<div>
<span
className="badge badge-info"
>
priority&gt;4
</span>
</div>
<div>
Match alerts with label
<code>
priority
</code>
value
<code>
&gt;
</code>
than
<code>
4
</code>
. Value will be casted to integer if possible, string comparision will be used as fallback.
</div>
</li>
</ul>
</dd>
</dl>
</div>
<div
className="mt-5"
>
<h4
className="text-center"
>
Filtering alerts using special filters
</h4>
<dl>
<dt>
Match alerts based on the Alertmanager instance name they were collected from
</dt>
<dd
className="mb-5"
>
<div>
Supported operators:
<kbd
className="mr-1"
>
=
</kbd>
<kbd
className="mr-1"
>
!=
</kbd>
<kbd
className="mr-1"
>
=~
</kbd>
<kbd
className="mr-1"
>
!~
</kbd>
</div>
<div>
Examples:
</div>
<ul>
<li>
<div>
<span
className="badge badge-info"
>
@alertmanager=prod
</span>
</div>
<div>
Match alerts collected from Alertmanager instance named
<code>
prod
</code>
.
</div>
</li>
<li>
<div>
<span
className="badge badge-info"
>
@alertmanager!=dev
</span>
</div>
<div>
Match alerts collected from Alertmanager instances except for the one named
<code>
dev
</code>
.
</div>
</li>
<li>
<div>
<span
className="badge badge-info"
>
@alertmanager=~prod
</span>
</div>
<div>
Match alerts collected from Alertmanager instances with names matching regular expression
<code>
/.*prod.*/
</code>
.
</div>
</li>
</ul>
</dd>
<dt>
Match alerts based on the receiver name
</dt>
<dd
className="mb-5"
>
<div>
Supported operators:
<kbd
className="mr-1"
>
=
</kbd>
<kbd
className="mr-1"
>
!=
</kbd>
<kbd
className="mr-1"
>
=~
</kbd>
<kbd
className="mr-1"
>
!~
</kbd>
</div>
<div>
Examples:
</div>
<ul>
<li>
<div>
<span
className="badge badge-info"
>
@receiver=default
</span>
</div>
<div>
Match alerts sent to the default receiver.
</div>
</li>
<li>
<div>
<span
className="badge badge-info"
>
@receiver!=hipchat
</span>
</div>
<div>
Match alerts not sent to the hipchat receiver.
</div>
</li>
<li>
<div>
<span
className="badge badge-info"
>
@receiver=~email
</span>
</div>
<div>
Match alerts sent to any receiver with name matching regular expression
<code>
/.*email.*/
</code>
.
</div>
</li>
</ul>
</dd>
<dt>
Match alerts based on the state
</dt>
<dd
className="mb-5"
>
<div>
Supported operators:
<kbd
className="mr-1"
>
=
</kbd>
<kbd
className="mr-1"
>
!=
</kbd>
</div>
<div>
Examples:
</div>
<ul>
<li>
<div>
<span
className="badge badge-info"
>
@state=active
</span>
</div>
<div>
Match only active alerts.
</div>
</li>
<li>
<div>
<span
className="badge badge-info"
>
@state!=active
</span>
</div>
<div>
Match alerts that are not active, only suppressed and unprocessed will be matched.
</div>
</li>
<li>
<div>
<span
className="badge badge-info"
>
@state=suppressed
</span>
</div>
<div>
Match only suppressed alerts.
</div>
</li>
<li>
<div>
<span
className="badge badge-info"
>
@state=unprocessed
</span>
</div>
<div>
Match only unprocessed alerts.
</div>
</li>
</ul>
</dd>
<dt>
Match alerts based on the author of silence
</dt>
<dd
className="mb-5"
>
<div>
Supported operators:
<kbd
className="mr-1"
>
=
</kbd>
<kbd
className="mr-1"
>
!=
</kbd>
<kbd
className="mr-1"
>
=~
</kbd>
<kbd
className="mr-1"
>
!~
</kbd>
</div>
<div>
Examples:
</div>
<ul>
<li>
<div>
<span
className="badge badge-info"
>
@silence_author=me@example.com
</span>
</div>
<div>
Match alerts silenced by
<code>
me@example.com
</code>
.
</div>
</li>
<li>
<div>
<span
className="badge badge-info"
>
@silence_author!=me@example.com
</span>
</div>
<div>
Match alerts silenced by everyone except
<code>
foo@example.com
</code>
.
</div>
</li>
<li>
<div>
<span
className="badge badge-info"
>
@silence_author=~@example.com
</span>
</div>
<div>
Match alerts silenced by author matching regular expression
<code>
/.*@example.com.*/
</code>
.
</div>
</li>
</ul>
</dd>
<dt>
Match alerts based on the jira linked in the silence
</dt>
<dd
className="mb-5"
>
<div>
Supported operators:
<kbd
className="mr-1"
>
=
</kbd>
<kbd
className="mr-1"
>
!=
</kbd>
<kbd
className="mr-1"
>
=~
</kbd>
<kbd
className="mr-1"
>
!~
</kbd>
</div>
<div>
Examples:
</div>
<ul>
<div
className="text-warning"
>
This is supported only if JIRA regexp are enabled and able to match JIRA ids in the silence comment body.
</div>
<li>
<div>
<span
className="badge badge-info"
>
@silence_jira=PROJECT-123
</span>
</div>
<div>
Match silenced alerts where detected JIRA issue id is equal to
<code>
PROJECT-123
</code>
.
</div>
</li>
<li>
<div>
<span
className="badge badge-info"
>
@silence_jira!=PROJECT-123
</span>
</div>
<div>
Match silenced alerts where detected JIRA issue id is different than
<code>
PROJECT-123
</code>
.
</div>
</li>
<li>
<div>
<span
className="badge badge-info"
>
@silence_jira=~PROJECT
</span>
</div>
<div>
Match silenced alerts where detected JIRA issue id matches regular expression
<code>
/.*PROJECT.*/
</code>
.
</div>
</li>
</ul>
</dd>
<dt>
Limit number of displayed alerts
</dt>
<dd
className="mb-5"
>
<div>
Supported operators:
<kbd
className="mr-1"
>
=
</kbd>
</div>
<div>
Examples:
</div>
<ul>
<div
className="text-warning"
>
Value must be a number &gt;= 1.
</div>
<li>
<div>
<span
className="badge badge-info"
>
@limit=10
</span>
</div>
<div>
Limit number of displayed alerts to 10.
</div>
</li>
</ul>
</dd>
<dt>
Match alerts based on creation timestamp
</dt>
<dd
className="mb-5"
>
<div>
Supported operators:
<kbd
className="mr-1"
>
&gt;
</kbd>
<kbd
className="mr-1"
>
&lt;
</kbd>
</div>
<div>
Examples:
</div>
<ul>
<li>
<div>
<span
className="badge badge-info"
>
@age&gt;15m
</span>
</div>
<div>
Match alerts older than 15 minutes.
</div>
</li>
<li>
<div>
<span
className="badge badge-info"
>
@age&gt;1h
</span>
</div>
<div>
Match alerts older than 1 hour.
</div>
</li>
<li>
<div>
<span
className="badge badge-info"
>
@age&lt;10h30m
</span>
</div>
<div>
Match alerts more recent than 10 hours and 30 minutes.
</div>
</li>
</ul>
</dd>
</dl>
</div>
</div>
`;

View File

@@ -0,0 +1,236 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<MainModalContent /> matches snapshot 1`] = `
<div
className="modal d-block bg-primary-transparent-80"
role="dialog"
>
<div
className="modal-dialog modal-lg"
role="document"
>
<div
className="modal-content"
>
<div
className="modal-header py-2"
>
<nav
className="nav nav-pills nav-justified w-100"
>
<a
className="nav-item nav-link cursor-pointer active"
onClick={[Function]}
>
Configuration
</a>
<a
className="nav-item nav-link cursor-pointer text-primary"
onClick={[Function]}
>
Help
</a>
<button
className="close"
onClick={[Function]}
type="button"
>
<span>
×
</span>
</button>
</nav>
</div>
<div
className="modal-body"
>
<form
className="px-3"
>
<div
className="form-group text-center"
>
<label
className="mb-4"
>
Refresh interval
</label>
<div
aria-disabled={false}
className="input-range"
onKeyDown={[Function]}
onKeyUp={[Function]}
onMouseDown={[Function]}
onTouchStart={[Function]}
>
<span
className="input-range__label input-range__label--min"
>
<span
className="input-range__label-container"
>
10s
</span>
</span>
<div
className="input-range__track input-range__track--background"
onMouseDown={[Function]}
onTouchStart={[Function]}
>
<div
className="input-range__track input-range__track--active"
style={
Object {
"left": "0%",
"width": "18.181818181818183%",
}
}
/>
<span
className="input-range__slider-container"
style={
Object {
"left": "18.181818181818183%",
"position": "absolute",
}
}
>
<span
className="input-range__label input-range__label--value"
>
<span
className="input-range__label-container"
>
30s
</span>
</span>
<div
aria-controls={undefined}
aria-labelledby={undefined}
aria-valuemax={120}
aria-valuemin={10}
aria-valuenow={30}
className="input-range__slider"
draggable="false"
onKeyDown={[Function]}
onMouseDown={[Function]}
onTouchStart={[Function]}
role="slider"
tabIndex="0"
/>
</span>
</div>
<span
className="input-range__label input-range__label--max"
>
<span
className="input-range__label-container"
>
120s
</span>
</span>
</div>
</div>
<div
className="mt-5"
/>
<div
className="form-group text-center"
>
<label
className="mb-4"
>
Default number of alerts to show per group
</label>
<div
aria-disabled={false}
className="input-range"
onKeyDown={[Function]}
onKeyUp={[Function]}
onMouseDown={[Function]}
onTouchStart={[Function]}
>
<span
className="input-range__label input-range__label--min"
>
<span
className="input-range__label-container"
>
1
</span>
</span>
<div
className="input-range__track input-range__track--background"
onMouseDown={[Function]}
onTouchStart={[Function]}
>
<div
className="input-range__track input-range__track--active"
style={
Object {
"left": "0%",
"width": "44.44444444444444%",
}
}
/>
<span
className="input-range__slider-container"
style={
Object {
"left": "44.44444444444444%",
"position": "absolute",
}
}
>
<span
className="input-range__label input-range__label--value"
>
<span
className="input-range__label-container"
>
5
</span>
</span>
<div
aria-controls={undefined}
aria-labelledby={undefined}
aria-valuemax={10}
aria-valuemin={1}
aria-valuenow={5}
className="input-range__slider"
draggable="false"
onKeyDown={[Function]}
onMouseDown={[Function]}
onTouchStart={[Function]}
role="slider"
tabIndex="0"
/>
</span>
</div>
<span
className="input-range__label input-range__label--max"
>
<span
className="input-range__label-container"
>
10
</span>
</span>
</div>
</div>
</form>
</div>
<div
className="modal-footer"
>
<span
className="text-muted"
>
Version:
unknown
</span>
</div>
</div>
</div>
</div>
`;

View File

@@ -0,0 +1,54 @@
import React from "react";
import sd from "skin-deep";
import { AlertStore } from "Stores/AlertStore";
import { Settings } from "Stores/Settings";
import { MainModal } from ".";
let alertStore;
let settingsStore;
beforeEach(() => {
alertStore = new AlertStore([]);
settingsStore = new Settings();
});
const RenderMainModal = () => {
return sd.shallowRender(
<MainModal alertStore={alertStore} settingsStore={settingsStore} />
);
};
describe("<MainModal />", () => {
it("only renders FontAwesomeIcon when modal is not shown", () => {
const tree = RenderMainModal();
// <Unknown/> is how React.Fragment gets rendered
expect(tree.text()).toBe("<Unknown /><FontAwesomeIcon />");
});
it("renders the modal when it is shown", () => {
const tree = RenderMainModal();
const instance = tree.getMountedInstance();
instance.toggle.toggle();
// <Unknown/> is how React.Fragment gets rendered
expect(tree.text()).toBe(
"<Unknown /><FontAwesomeIcon /><MainModalContent />"
);
});
it("hides the modal when toggle() is called twice", () => {
const tree = RenderMainModal();
const instance = tree.getMountedInstance();
instance.toggle.toggle();
instance.toggle.toggle();
expect(tree.text()).toBe("<Unknown /><FontAwesomeIcon />");
});
it("hides the modal when hide() is called", () => {
const tree = RenderMainModal();
const instance = tree.getMountedInstance();
instance.toggle.show = true;
instance.toggle.hide();
expect(tree.text()).toBe("<Unknown /><FontAwesomeIcon />");
});
});