mirror of
https://github.com/prymitive/karma
synced 2026-05-07 03:26:52 +00:00
Merge pull request #84 from prymitive/grid-tests
Alert grid components test coverage
This commit is contained in:
2
Makefile
2
Makefile
@@ -76,6 +76,7 @@ clean:
|
||||
run: $(NAME)
|
||||
ALERTMANAGER_INTERVAL=36000h \
|
||||
ALERTMANAGER_URI=$(ALERTMANAGER_URI) \
|
||||
ANNOTATIONS_HIDDEN="help" \
|
||||
LABELS_COLOR_UNIQUE="@receiver instance cluster" \
|
||||
LABELS_COLOR_STATIC="job" \
|
||||
FILTERS_DEFAULT="@state=active @receiver=by-cluster-service" \
|
||||
@@ -94,6 +95,7 @@ run-docker: docker-image
|
||||
-v $(MOCK_PATH):$(MOCK_PATH) \
|
||||
-e ALERTMANAGER_INTERVAL=36000h \
|
||||
-e ALERTMANAGER_URI=$(ALERTMANAGER_URI) \
|
||||
-e ANNOTATIONS_HIDDEN="help" \
|
||||
-e LABELS_COLOR_UNIQUE="instance cluster" \
|
||||
-e LABELS_COLOR_STATIC="job" \
|
||||
-e FILTERS_DEFAULT="@state=active @receiver=by-cluster-service" \
|
||||
|
||||
6
ui/__mocks__/copy-to-clipboard.js
Normal file
6
ui/__mocks__/copy-to-clipboard.js
Normal file
@@ -0,0 +1,6 @@
|
||||
// mock copy-to-clipboard since it throws errors in tests
|
||||
// and we don't really need to copy anything, only ensure we're calling it
|
||||
|
||||
const copy = jest.fn();
|
||||
|
||||
export default copy;
|
||||
40
ui/package-lock.json
generated
40
ui/package-lock.json
generated
@@ -3100,6 +3100,40 @@
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
|
||||
"integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA=="
|
||||
},
|
||||
"diffable-html": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/diffable-html/-/diffable-html-3.0.0.tgz",
|
||||
"integrity": "sha512-lUxHiU00DexR/wKcY56OiJZmB0D66ghidYfU4VxUMG09TDx+1jjO7/dFrZKI2p9z00tWY/7ZeO9BBEi6n0jUYQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"htmlparser2": "3.9.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"domhandler": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz",
|
||||
"integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"domelementtype": "1.3.0"
|
||||
}
|
||||
},
|
||||
"htmlparser2": {
|
||||
"version": "3.9.2",
|
||||
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.9.2.tgz",
|
||||
"integrity": "sha1-G9+HrMoPP55T+k/M6w9LTLsAszg=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"domelementtype": "1.3.0",
|
||||
"domhandler": "2.4.2",
|
||||
"domutils": "1.5.1",
|
||||
"entities": "1.1.1",
|
||||
"inherits": "2.0.3",
|
||||
"readable-stream": "2.3.6"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"diffie-hellman": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
|
||||
@@ -6534,6 +6568,12 @@
|
||||
"pretty-format": "20.0.3"
|
||||
}
|
||||
},
|
||||
"jest-date-mock": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/jest-date-mock/-/jest-date-mock-1.0.3.tgz",
|
||||
"integrity": "sha512-PLwqL0KI+zDKc6SoytvApudwFD8uDLOM7Bf4Z5C3KpJrHDJv5RawgSZUQOUqSukQ+TOhdHzliUOvGtE3aA+fWA==",
|
||||
"dev": true
|
||||
},
|
||||
"jest-diff": {
|
||||
"version": "20.0.3",
|
||||
"resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-20.0.3.tgz",
|
||||
|
||||
@@ -51,11 +51,13 @@
|
||||
"watch-css": "npm run build-css && node_modules/.bin/node-sass-chokidar src/ -o src/ --watch --recursive"
|
||||
},
|
||||
"devDependencies": {
|
||||
"diffable-html": "3.0.0",
|
||||
"enzyme": "3.5.0",
|
||||
"enzyme-adapter-react-16": "1.3.1",
|
||||
"enzyme-to-json": "3.3.4",
|
||||
"eslint-plugin-react": "7.11.1",
|
||||
"jest-canvas-mock": "1.1.0",
|
||||
"jest-date-mock": "1.0.3",
|
||||
"jest-fetch-mock": "1.6.5",
|
||||
"jest-localstorage-mock": "2.2.0",
|
||||
"jest-mock-console": "0.4.0",
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<Alert /> matches snapshot with showAlertmanagers=false showReceiver=false 1`] = `
|
||||
"
|
||||
<li class=\\"components-grid-alertgrid-alertgroup-alert list-group-item pl-1 pr-0 py-0 my-1 rounded-0 border-left-1 border-right-0 border-top-0 border-bottom-0 border-danger\\">
|
||||
<div class=\\"mb-1\\">
|
||||
<div class=\\"mr-1 mb-1 p-1 bg-light cursor-pointer d-inline-block rounded components-grid-annotation\\">
|
||||
<svg aria-hidden=\\"true\\"
|
||||
data-prefix=\\"fas\\"
|
||||
data-icon=\\"search-minus\\"
|
||||
class=\\"svg-inline--fa fa-search-minus fa-w-16 mr-1\\"
|
||||
role=\\"img\\"
|
||||
xmlns=\\"http://www.w3.org/2000/svg\\"
|
||||
viewbox=\\"0 0 512 512\\"
|
||||
>
|
||||
<path fill=\\"currentColor\\"
|
||||
d=\\"M304 192v32c0 6.6-5.4 12-12 12H124c-6.6 0-12-5.4-12-12v-32c0-6.6 5.4-12 12-12h168c6.6 0 12 5.4 12 12zm201 284.7L476.7 505c-9.4 9.4-24.6 9.4-33.9 0L343 405.3c-4.5-4.5-7-10.6-7-17V372c-35.3 27.6-79.7 44-128 44C93.1 416 0 322.9 0 208S93.1 0 208 0s208 93.1 208 208c0 48.3-16.4 92.7-44 128h16.3c6.4 0 12.5 2.5 17 7l99.7 99.7c9.3 9.4 9.3 24.6 0 34zM344 208c0-75.2-60.8-136-136-136S72 132.8 72 208s60.8 136 136 136 136-60.8 136-136z\\"
|
||||
>
|
||||
</path>
|
||||
</svg>
|
||||
<span class=\\"text-muted\\">
|
||||
help:
|
||||
</span>
|
||||
<span class=\\"Linkify\\">
|
||||
some long text
|
||||
</span>
|
||||
</div>
|
||||
<div class=\\"mr-1 mb-1 p-1 bg-light cursor-pointer d-inline-block rounded components-grid-annotation\\">
|
||||
<svg aria-hidden=\\"true\\"
|
||||
data-prefix=\\"fas\\"
|
||||
data-icon=\\"search-plus\\"
|
||||
class=\\"svg-inline--fa fa-search-plus fa-w-16 mr-1\\"
|
||||
role=\\"img\\"
|
||||
xmlns=\\"http://www.w3.org/2000/svg\\"
|
||||
viewbox=\\"0 0 512 512\\"
|
||||
>
|
||||
<path fill=\\"currentColor\\"
|
||||
d=\\"M304 192v32c0 6.6-5.4 12-12 12h-56v56c0 6.6-5.4 12-12 12h-32c-6.6 0-12-5.4-12-12v-56h-56c-6.6 0-12-5.4-12-12v-32c0-6.6 5.4-12 12-12h56v-56c0-6.6 5.4-12 12-12h32c6.6 0 12 5.4 12 12v56h56c6.6 0 12 5.4 12 12zm201 284.7L476.7 505c-9.4 9.4-24.6 9.4-33.9 0L343 405.3c-4.5-4.5-7-10.6-7-17V372c-35.3 27.6-79.7 44-128 44C93.1 416 0 322.9 0 208S93.1 0 208 0s208 93.1 208 208c0 48.3-16.4 92.7-44 128h16.3c6.4 0 12.5 2.5 17 7l99.7 99.7c9.3 9.4 9.3 24.6 0 34zM344 208c0-75.2-60.8-136-136-136S72 132.8 72 208s60.8 136 136 136 136-60.8 136-136z\\"
|
||||
>
|
||||
</path>
|
||||
</svg>
|
||||
hidden
|
||||
</div>
|
||||
</div>
|
||||
<span class=\\"text-nowrap text-truncate px-1 mr-1 badge badge-secondary\\">
|
||||
<time datetime=\\"1534268200017\\">
|
||||
a day ago
|
||||
</time>
|
||||
</span>
|
||||
<span class=\\"components-label components-label-with-hover text-nowrap text-truncate badge badge-warning mw-100\\">
|
||||
job: node_exporter
|
||||
</span>
|
||||
<span class=\\"components-label components-label-with-hover text-nowrap text-truncate badge badge-warning mw-100\\">
|
||||
cluster: dev
|
||||
</span>
|
||||
<a href=\\"http://localhost\\"
|
||||
target=\\"_blank\\"
|
||||
rel=\\"noopener noreferrer\\"
|
||||
class=\\"text-nowrap text-truncate badge badge-secondary mr-1\\"
|
||||
>
|
||||
<svg aria-hidden=\\"true\\"
|
||||
data-prefix=\\"fas\\"
|
||||
data-icon=\\"external-link-alt\\"
|
||||
class=\\"svg-inline--fa fa-external-link-alt fa-w-18 \\"
|
||||
role=\\"img\\"
|
||||
xmlns=\\"http://www.w3.org/2000/svg\\"
|
||||
viewbox=\\"0 0 576 512\\"
|
||||
>
|
||||
<path fill=\\"currentColor\\"
|
||||
d=\\"M576 24v127.984c0 21.461-25.96 31.98-40.971 16.971l-35.707-35.709-243.523 243.523c-9.373 9.373-24.568 9.373-33.941 0l-22.627-22.627c-9.373-9.373-9.373-24.569 0-33.941L442.756 76.676l-35.703-35.705C391.982 25.9 402.656 0 424.024 0H552c13.255 0 24 10.745 24 24zM407.029 270.794l-16 16A23.999 23.999 0 0 0 384 303.765V448H64V128h264a24.003 24.003 0 0 0 16.97-7.029l16-16C376.089 89.851 365.381 64 344 64H48C21.49 64 0 85.49 0 112v352c0 26.51 21.49 48 48 48h352c26.51 0 48-21.49 48-48V287.764c0-21.382-25.852-32.09-40.971-16.97z\\"
|
||||
>
|
||||
</path>
|
||||
</svg>
|
||||
link
|
||||
</a>
|
||||
</li>
|
||||
"
|
||||
`;
|
||||
@@ -50,6 +50,7 @@ const Alert = observer(
|
||||
key={a.name}
|
||||
name={a.name}
|
||||
value={a.value}
|
||||
visible={a.visible}
|
||||
afterUpdate={afterUpdate}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
import React from "react";
|
||||
|
||||
import { Provider } from "mobx-react";
|
||||
|
||||
import { mount } from "enzyme";
|
||||
|
||||
import { advanceTo, clear } from "jest-date-mock";
|
||||
|
||||
import toDiffableHtml from "diffable-html";
|
||||
|
||||
import { MockAlert, MockAnnotation } from "__mocks__/Alerts.js";
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { Alert } from ".";
|
||||
|
||||
let alertStore;
|
||||
|
||||
beforeEach(() => {
|
||||
advanceTo(new Date(2018, 7, 15, 20, 40, 0));
|
||||
alertStore = new AlertStore([]);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// reset Date() to current time
|
||||
clear();
|
||||
});
|
||||
|
||||
const MockAfterUpdate = jest.fn();
|
||||
|
||||
const MockedAlert = () => {
|
||||
return MockAlert(
|
||||
[
|
||||
MockAnnotation("help", "some long text", true, false),
|
||||
MockAnnotation("hidden", "some hidden text", false, false),
|
||||
MockAnnotation("link", "http://localhost", true, true)
|
||||
],
|
||||
{ job: "node_exporter", cluster: "dev" },
|
||||
"active"
|
||||
);
|
||||
};
|
||||
|
||||
const MountedAlert = (alert, showAlertmanagers, showReceiver) => {
|
||||
return mount(
|
||||
<Provider alertStore={alertStore}>
|
||||
<Alert
|
||||
alert={alert}
|
||||
showAlertmanagers={showAlertmanagers}
|
||||
showReceiver={showReceiver}
|
||||
afterUpdate={MockAfterUpdate}
|
||||
/>
|
||||
</Provider>
|
||||
);
|
||||
};
|
||||
|
||||
describe("<Alert />", () => {
|
||||
it("matches snapshot with showAlertmanagers=false showReceiver=false", () => {
|
||||
const alert = MockedAlert();
|
||||
const tree = MountedAlert(alert, false, false);
|
||||
expect(toDiffableHtml(tree.html())).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders @alertmanager label with showAlertmanagers=true", () => {
|
||||
const alert = MockedAlert();
|
||||
const tree = MountedAlert(alert, true, false);
|
||||
const label = tree
|
||||
.find("FilteringLabel")
|
||||
.filterWhere(elem => elem.props().name === "@alertmanager");
|
||||
expect(label.text()).toBe("@alertmanager: default");
|
||||
});
|
||||
|
||||
it("renders @receiver label with showReceiver=true", () => {
|
||||
const alert = MockedAlert();
|
||||
const tree = MountedAlert(alert, false, true);
|
||||
const label = tree
|
||||
.find("FilteringLabel")
|
||||
.filterWhere(elem => elem.props().name === "@receiver");
|
||||
expect(label.text()).toBe("@receiver: by-name");
|
||||
});
|
||||
|
||||
it("renders a silence if alert is silenced", () => {
|
||||
const alert = MockedAlert();
|
||||
alert.alertmanager[0].silencedBy = ["silence123456789"];
|
||||
const tree = MountedAlert(alert, false, false);
|
||||
const silence = tree.find("Silence");
|
||||
expect(silence).toHaveLength(1);
|
||||
expect(silence.html()).toMatch(/silence123456789/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,90 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<RenderLinkAnnotation /> matches snapshot 1`] = `
|
||||
<a
|
||||
className="text-nowrap text-truncate badge badge-secondary mr-1"
|
||||
href="http://localhost/foo"
|
||||
key="annotation name"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
border={false}
|
||||
className=""
|
||||
fixedWidth={false}
|
||||
flip={null}
|
||||
icon={
|
||||
Object {
|
||||
"icon": Array [
|
||||
576,
|
||||
512,
|
||||
Array [],
|
||||
"f35d",
|
||||
"M576 24v127.984c0 21.461-25.96 31.98-40.971 16.971l-35.707-35.709-243.523 243.523c-9.373 9.373-24.568 9.373-33.941 0l-22.627-22.627c-9.373-9.373-9.373-24.569 0-33.941L442.756 76.676l-35.703-35.705C391.982 25.9 402.656 0 424.024 0H552c13.255 0 24 10.745 24 24zM407.029 270.794l-16 16A23.999 23.999 0 0 0 384 303.765V448H64V128h264a24.003 24.003 0 0 0 16.97-7.029l16-16C376.089 89.851 365.381 64 344 64H48C21.49 64 0 85.49 0 112v352c0 26.51 21.49 48 48 48h352c26.51 0 48-21.49 48-48V287.764c0-21.382-25.852-32.09-40.971-16.97z",
|
||||
],
|
||||
"iconName": "external-link-alt",
|
||||
"prefix": "fas",
|
||||
}
|
||||
}
|
||||
inverse={false}
|
||||
listItem={false}
|
||||
mask={null}
|
||||
pull={null}
|
||||
pulse={false}
|
||||
rotation={null}
|
||||
size={null}
|
||||
spin={false}
|
||||
symbol={false}
|
||||
transform={null}
|
||||
/>
|
||||
|
||||
annotation name
|
||||
</a>
|
||||
`;
|
||||
|
||||
exports[`<RenderNonLinkAnnotation /> matches snapshot when visible=false 1`] = `
|
||||
"
|
||||
<div class=\\"mr-1 mb-1 p-1 bg-light cursor-pointer d-inline-block rounded components-grid-annotation\\">
|
||||
<svg aria-hidden=\\"true\\"
|
||||
data-prefix=\\"fas\\"
|
||||
data-icon=\\"search-plus\\"
|
||||
class=\\"svg-inline--fa fa-search-plus fa-w-16 mr-1\\"
|
||||
role=\\"img\\"
|
||||
xmlns=\\"http://www.w3.org/2000/svg\\"
|
||||
viewbox=\\"0 0 512 512\\"
|
||||
>
|
||||
<path fill=\\"currentColor\\"
|
||||
d=\\"M304 192v32c0 6.6-5.4 12-12 12h-56v56c0 6.6-5.4 12-12 12h-32c-6.6 0-12-5.4-12-12v-56h-56c-6.6 0-12-5.4-12-12v-32c0-6.6 5.4-12 12-12h56v-56c0-6.6 5.4-12 12-12h32c6.6 0 12 5.4 12 12v56h56c6.6 0 12 5.4 12 12zm201 284.7L476.7 505c-9.4 9.4-24.6 9.4-33.9 0L343 405.3c-4.5-4.5-7-10.6-7-17V372c-35.3 27.6-79.7 44-128 44C93.1 416 0 322.9 0 208S93.1 0 208 0s208 93.1 208 208c0 48.3-16.4 92.7-44 128h16.3c6.4 0 12.5 2.5 17 7l99.7 99.7c9.3 9.4 9.3 24.6 0 34zM344 208c0-75.2-60.8-136-136-136S72 132.8 72 208s60.8 136 136 136 136-60.8 136-136z\\"
|
||||
>
|
||||
</path>
|
||||
</svg>
|
||||
foo
|
||||
</div>
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`<RenderNonLinkAnnotation /> matches snapshot when visible=true 1`] = `
|
||||
"
|
||||
<div class=\\"mr-1 mb-1 p-1 bg-light cursor-pointer d-inline-block rounded components-grid-annotation\\">
|
||||
<svg aria-hidden=\\"true\\"
|
||||
data-prefix=\\"fas\\"
|
||||
data-icon=\\"search-minus\\"
|
||||
class=\\"svg-inline--fa fa-search-minus fa-w-16 mr-1\\"
|
||||
role=\\"img\\"
|
||||
xmlns=\\"http://www.w3.org/2000/svg\\"
|
||||
viewbox=\\"0 0 512 512\\"
|
||||
>
|
||||
<path fill=\\"currentColor\\"
|
||||
d=\\"M304 192v32c0 6.6-5.4 12-12 12H124c-6.6 0-12-5.4-12-12v-32c0-6.6 5.4-12 12-12h168c6.6 0 12 5.4 12 12zm201 284.7L476.7 505c-9.4 9.4-24.6 9.4-33.9 0L343 405.3c-4.5-4.5-7-10.6-7-17V372c-35.3 27.6-79.7 44-128 44C93.1 416 0 322.9 0 208S93.1 0 208 0s208 93.1 208 208c0 48.3-16.4 92.7-44 128h16.3c6.4 0 12.5 2.5 17 7l99.7 99.7c9.3 9.4 9.3 24.6 0 34zM344 208c0-75.2-60.8-136-136-136S72 132.8 72 208s60.8 136 136 136 136-60.8 136-136z\\"
|
||||
>
|
||||
</path>
|
||||
</svg>
|
||||
<span class=\\"text-muted\\">
|
||||
foo:
|
||||
</span>
|
||||
<span class=\\"Linkify\\">
|
||||
some long text
|
||||
</span>
|
||||
</div>
|
||||
"
|
||||
`;
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { Component } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import { observable, action, toJS } from "mobx";
|
||||
import { observable, action } from "mobx";
|
||||
import { observer, inject } from "mobx-react";
|
||||
|
||||
import Linkify from "react-linkify";
|
||||
@@ -20,6 +20,7 @@ const RenderNonLinkAnnotation = inject("alertStore")(
|
||||
alertStore: PropTypes.object.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
value: PropTypes.string.isRequired,
|
||||
visible: PropTypes.bool.isRequired,
|
||||
afterUpdate: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
@@ -45,7 +46,7 @@ const RenderNonLinkAnnotation = inject("alertStore")(
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.toggle.visible = this.isVisible();
|
||||
this.toggle.visible = props.visible;
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
@@ -54,34 +55,6 @@ const RenderNonLinkAnnotation = inject("alertStore")(
|
||||
afterUpdate();
|
||||
}
|
||||
|
||||
// determinate if this annotation should be hidden by default or not
|
||||
isVisible() {
|
||||
const { alertStore, name } = this.props;
|
||||
|
||||
const annotationsHidden = toJS(
|
||||
alertStore.settings.values.annotationsHidden
|
||||
);
|
||||
const isInHidden =
|
||||
annotationsHidden !== null && annotationsHidden.indexOf(name) >= 0;
|
||||
|
||||
const annotationsVisible = toJS(
|
||||
alertStore.settings.values.annotationsVisible
|
||||
);
|
||||
const isInVisible =
|
||||
annotationsVisible !== null && annotationsVisible.indexOf(name) >= 0;
|
||||
|
||||
if (isInVisible) return true;
|
||||
|
||||
if (
|
||||
toJS(alertStore.settings.values.annotationsDefaultHidden) === true ||
|
||||
isInHidden === true
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { name, value } = this.props;
|
||||
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
import React from "react";
|
||||
|
||||
import { shallow, mount } from "enzyme";
|
||||
|
||||
import toDiffableHtml from "diffable-html";
|
||||
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { RenderNonLinkAnnotation, RenderLinkAnnotation } from ".";
|
||||
|
||||
let alertStore;
|
||||
|
||||
beforeEach(() => {
|
||||
alertStore = new AlertStore([]);
|
||||
});
|
||||
|
||||
const ShallowLinkAnnotation = () => {
|
||||
return shallow(
|
||||
<RenderLinkAnnotation name="annotation name" value="http://localhost/foo" />
|
||||
);
|
||||
};
|
||||
|
||||
describe("<RenderLinkAnnotation />", () => {
|
||||
it("matches snapshot", () => {
|
||||
const tree = ShallowLinkAnnotation();
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("contains a link", () => {
|
||||
const tree = ShallowLinkAnnotation();
|
||||
const link = tree.find("a[href='http://localhost/foo']");
|
||||
expect(link).toHaveLength(1);
|
||||
expect(link.text()).toMatch(/annotation name/);
|
||||
});
|
||||
});
|
||||
|
||||
const MockAfterUpdate = jest.fn();
|
||||
|
||||
const ShallowNonLinkAnnotation = visible => {
|
||||
return shallow(
|
||||
<RenderNonLinkAnnotation
|
||||
alertStore={alertStore}
|
||||
name="foo"
|
||||
value="some long text"
|
||||
visible={visible}
|
||||
afterUpdate={MockAfterUpdate}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const MountedNonLinkAnnotation = visible => {
|
||||
return mount(
|
||||
<RenderNonLinkAnnotation
|
||||
alertStore={alertStore}
|
||||
name="foo"
|
||||
value="some long text"
|
||||
visible={visible}
|
||||
afterUpdate={MockAfterUpdate}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
describe("<RenderNonLinkAnnotation />", () => {
|
||||
it("matches snapshot when visible=true", () => {
|
||||
const tree = ShallowNonLinkAnnotation(true);
|
||||
expect(toDiffableHtml(tree.html())).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("contains value when visible=true", () => {
|
||||
const tree = ShallowNonLinkAnnotation(true);
|
||||
expect(tree.html()).toMatch(/some long text/);
|
||||
});
|
||||
|
||||
it("matches snapshot when visible=false", () => {
|
||||
const tree = ShallowNonLinkAnnotation(false);
|
||||
expect(toDiffableHtml(tree.html())).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("doesn't contain value when visible=false", () => {
|
||||
const tree = ShallowNonLinkAnnotation(false);
|
||||
expect(tree.html()).not.toMatch(/some long text/);
|
||||
});
|
||||
|
||||
it("clicking on + icon hides the value", () => {
|
||||
const tree = MountedNonLinkAnnotation(true);
|
||||
expect(tree.html()).toMatch(/fa-search-minus/);
|
||||
expect(tree.html()).toMatch(/some long text/);
|
||||
tree.find("div").simulate("click");
|
||||
expect(tree.html()).toMatch(/fa-search-plus/);
|
||||
expect(tree.html()).not.toMatch(/some long text/);
|
||||
});
|
||||
|
||||
it("clicking on - icon shows the value", () => {
|
||||
const tree = MountedNonLinkAnnotation(false);
|
||||
expect(tree.html()).toMatch(/fa-search-plus/);
|
||||
expect(tree.html()).not.toMatch(/some long text/);
|
||||
tree.find("div").simulate("click");
|
||||
expect(tree.html()).toMatch(/fa-search-minus/);
|
||||
expect(tree.html()).toMatch(/some long text/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,79 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<GroupFooter /> matches snapshot 1`] = `
|
||||
"
|
||||
<div class=\\"card-footer px-2 py-1\\">
|
||||
<div class=\\"mb-1\\">
|
||||
<div class=\\"mr-1 mb-1 p-1 bg-light cursor-pointer d-inline-block rounded components-grid-annotation\\">
|
||||
<svg aria-hidden=\\"true\\"
|
||||
data-prefix=\\"fas\\"
|
||||
data-icon=\\"search-minus\\"
|
||||
class=\\"svg-inline--fa fa-search-minus fa-w-16 mr-1\\"
|
||||
role=\\"img\\"
|
||||
xmlns=\\"http://www.w3.org/2000/svg\\"
|
||||
viewbox=\\"0 0 512 512\\"
|
||||
>
|
||||
<path fill=\\"currentColor\\"
|
||||
d=\\"M304 192v32c0 6.6-5.4 12-12 12H124c-6.6 0-12-5.4-12-12v-32c0-6.6 5.4-12 12-12h168c6.6 0 12 5.4 12 12zm201 284.7L476.7 505c-9.4 9.4-24.6 9.4-33.9 0L343 405.3c-4.5-4.5-7-10.6-7-17V372c-35.3 27.6-79.7 44-128 44C93.1 416 0 322.9 0 208S93.1 0 208 0s208 93.1 208 208c0 48.3-16.4 92.7-44 128h16.3c6.4 0 12.5 2.5 17 7l99.7 99.7c9.3 9.4 9.3 24.6 0 34zM344 208c0-75.2-60.8-136-136-136S72 132.8 72 208s60.8 136 136 136 136-60.8 136-136z\\"
|
||||
>
|
||||
</path>
|
||||
</svg>
|
||||
<span class=\\"text-muted\\">
|
||||
summary:
|
||||
</span>
|
||||
<span class=\\"Linkify\\">
|
||||
This is summary
|
||||
</span>
|
||||
</div>
|
||||
<div class=\\"mr-1 mb-1 p-1 bg-light cursor-pointer d-inline-block rounded components-grid-annotation\\">
|
||||
<svg aria-hidden=\\"true\\"
|
||||
data-prefix=\\"fas\\"
|
||||
data-icon=\\"search-plus\\"
|
||||
class=\\"svg-inline--fa fa-search-plus fa-w-16 mr-1\\"
|
||||
role=\\"img\\"
|
||||
xmlns=\\"http://www.w3.org/2000/svg\\"
|
||||
viewbox=\\"0 0 512 512\\"
|
||||
>
|
||||
<path fill=\\"currentColor\\"
|
||||
d=\\"M304 192v32c0 6.6-5.4 12-12 12h-56v56c0 6.6-5.4 12-12 12h-32c-6.6 0-12-5.4-12-12v-56h-56c-6.6 0-12-5.4-12-12v-32c0-6.6 5.4-12 12-12h56v-56c0-6.6 5.4-12 12-12h32c6.6 0 12 5.4 12 12v56h56c6.6 0 12 5.4 12 12zm201 284.7L476.7 505c-9.4 9.4-24.6 9.4-33.9 0L343 405.3c-4.5-4.5-7-10.6-7-17V372c-35.3 27.6-79.7 44-128 44C93.1 416 0 322.9 0 208S93.1 0 208 0s208 93.1 208 208c0 48.3-16.4 92.7-44 128h16.3c6.4 0 12.5 2.5 17 7l99.7 99.7c9.3 9.4 9.3 24.6 0 34zM344 208c0-75.2-60.8-136-136-136S72 132.8 72 208s60.8 136 136 136 136-60.8 136-136z\\"
|
||||
>
|
||||
</path>
|
||||
</svg>
|
||||
hidden
|
||||
</div>
|
||||
</div>
|
||||
<span class=\\"components-label components-label-with-hover text-nowrap text-truncate badge badge-warning mw-100\\">
|
||||
label1: foo
|
||||
</span>
|
||||
<span class=\\"components-label components-label-with-hover text-nowrap text-truncate badge badge-warning mw-100\\">
|
||||
label2: bar
|
||||
</span>
|
||||
<span class=\\"components-label components-label-with-hover text-nowrap text-truncate badge badge-warning mw-100\\">
|
||||
@alertmanager: default
|
||||
</span>
|
||||
<span class=\\"components-label components-label-with-hover text-nowrap text-truncate badge badge-warning mw-100\\">
|
||||
@receiver: by-name
|
||||
</span>
|
||||
<a href=\\"http://link.example.com\\"
|
||||
target=\\"_blank\\"
|
||||
rel=\\"noopener noreferrer\\"
|
||||
class=\\"text-nowrap text-truncate badge badge-secondary mr-1\\"
|
||||
>
|
||||
<svg aria-hidden=\\"true\\"
|
||||
data-prefix=\\"fas\\"
|
||||
data-icon=\\"external-link-alt\\"
|
||||
class=\\"svg-inline--fa fa-external-link-alt fa-w-18 \\"
|
||||
role=\\"img\\"
|
||||
xmlns=\\"http://www.w3.org/2000/svg\\"
|
||||
viewbox=\\"0 0 576 512\\"
|
||||
>
|
||||
<path fill=\\"currentColor\\"
|
||||
d=\\"M576 24v127.984c0 21.461-25.96 31.98-40.971 16.971l-35.707-35.709-243.523 243.523c-9.373 9.373-24.568 9.373-33.941 0l-22.627-22.627c-9.373-9.373-9.373-24.569 0-33.941L442.756 76.676l-35.703-35.705C391.982 25.9 402.656 0 424.024 0H552c13.255 0 24 10.745 24 24zM407.029 270.794l-16 16A23.999 23.999 0 0 0 384 303.765V448H64V128h264a24.003 24.003 0 0 0 16.97-7.029l16-16C376.089 89.851 365.381 64 344 64H48C21.49 64 0 85.49 0 112v352c0 26.51 21.49 48 48 48h352c26.51 0 48-21.49 48-48V287.764c0-21.382-25.852-32.09-40.971-16.97z\\"
|
||||
>
|
||||
</path>
|
||||
</svg>
|
||||
link
|
||||
</a>
|
||||
</div>
|
||||
"
|
||||
`;
|
||||
@@ -28,6 +28,7 @@ const GroupFooter = observer(
|
||||
key={a.name}
|
||||
name={a.name}
|
||||
value={a.value}
|
||||
visible={a.visible}
|
||||
afterUpdate={afterUpdate}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import React from "react";
|
||||
|
||||
import { Provider } from "mobx-react";
|
||||
|
||||
import { mount } from "enzyme";
|
||||
|
||||
import toDiffableHtml from "diffable-html";
|
||||
|
||||
import { MockAlertGroup, MockAnnotation } from "__mocks__/Alerts.js";
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { GroupFooter } from ".";
|
||||
|
||||
let group;
|
||||
let alertStore;
|
||||
|
||||
const MockGroup = () => {
|
||||
const group = MockAlertGroup(
|
||||
{ alertname: "Fake Alert" },
|
||||
[],
|
||||
[
|
||||
MockAnnotation("summary", "This is summary", true, false),
|
||||
MockAnnotation("hidden", "This is hidden annotation", false, false),
|
||||
MockAnnotation("link", "http://link.example.com", true, true)
|
||||
],
|
||||
{ label1: "foo", label2: "bar" }
|
||||
);
|
||||
return group;
|
||||
};
|
||||
|
||||
const MockAfterUpdate = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
alertStore = new AlertStore([]);
|
||||
group = MockGroup();
|
||||
});
|
||||
|
||||
const MountedGroupFooter = () => {
|
||||
return mount(
|
||||
<Provider alertStore={alertStore}>
|
||||
<GroupFooter
|
||||
group={group}
|
||||
alertmanagers={["default"]}
|
||||
afterUpdate={MockAfterUpdate}
|
||||
/>
|
||||
</Provider>
|
||||
);
|
||||
};
|
||||
|
||||
describe("<GroupFooter />", () => {
|
||||
it("matches snapshot", () => {
|
||||
const tree = MountedGroupFooter().find("GroupFooter");
|
||||
expect(toDiffableHtml(tree.html())).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -150,4 +150,4 @@ const GroupMenu = observer(
|
||||
}
|
||||
);
|
||||
|
||||
export { GroupMenu };
|
||||
export { GroupMenu, MenuContent };
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import React from "react";
|
||||
|
||||
import { mount } from "enzyme";
|
||||
|
||||
import copy from "copy-to-clipboard";
|
||||
|
||||
import { MockAlertGroup } from "__mocks__/Alerts.js";
|
||||
import { SilenceFormStore } from "Stores/SilenceFormStore";
|
||||
import { GroupMenu, MenuContent } from "./GroupMenu";
|
||||
|
||||
let silenceFormStore;
|
||||
|
||||
beforeEach(() => {
|
||||
silenceFormStore = new SilenceFormStore();
|
||||
});
|
||||
|
||||
const MockAfterClick = jest.fn();
|
||||
|
||||
const MountedGroupMenu = group => {
|
||||
return mount(<GroupMenu group={group} silenceFormStore={silenceFormStore} />);
|
||||
};
|
||||
|
||||
describe("<GroupMenu />", () => {
|
||||
it("is collapsed by default", () => {
|
||||
const group = MockAlertGroup({ alertname: "Fake Alert" }, [], [], {});
|
||||
const tree = MountedGroupMenu(group);
|
||||
expect(tree.instance().collapse.value).toBe(true);
|
||||
});
|
||||
|
||||
it("clicking toggle sets collapse value to 'false'", () => {
|
||||
const group = MockAlertGroup({ alertname: "Fake Alert" }, [], [], {});
|
||||
const tree = MountedGroupMenu(group);
|
||||
const toggle = tree.find("a.cursor-pointer");
|
||||
toggle.simulate("click");
|
||||
expect(tree.instance().collapse.value).toBe(false);
|
||||
});
|
||||
|
||||
it("handleClickOutside() call sets collapse value to 'true'", () => {
|
||||
const group = MockAlertGroup({ alertname: "Fake Alert" }, [], [], {});
|
||||
const tree = MountedGroupMenu(group);
|
||||
|
||||
const toggle = tree.find("a.cursor-pointer");
|
||||
toggle.simulate("click");
|
||||
expect(tree.instance().collapse.value).toBe(false);
|
||||
|
||||
tree.instance().handleClickOutside();
|
||||
|
||||
expect(tree.instance().collapse.value).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
const MountedMenuContent = group => {
|
||||
return mount(
|
||||
<MenuContent
|
||||
popperPlacement="top"
|
||||
popperRef={null}
|
||||
popperStyle={{}}
|
||||
group={group}
|
||||
afterClick={MockAfterClick}
|
||||
silenceFormStore={silenceFormStore}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
describe("<MenuContent />", () => {
|
||||
it("clicking on 'Copy' icon copies the link to clickboard", () => {
|
||||
const group = MockAlertGroup({ alertname: "Fake Alert" }, [], [], {});
|
||||
const tree = MountedMenuContent(group);
|
||||
const button = tree.find(".dropdown-item").at(0);
|
||||
button.simulate("click");
|
||||
expect(copy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("clicking on 'Silence' icon opens the silence form modal", () => {
|
||||
const group = MockAlertGroup({ alertname: "Fake Alert" }, [], [], {});
|
||||
const tree = MountedMenuContent(group);
|
||||
const button = tree.find(".dropdown-item").at(1);
|
||||
button.simulate("click");
|
||||
expect(silenceFormStore.toggle.visible).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,118 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<Silence /> matches snapshot when data is not present in alertStore 1`] = `
|
||||
"
|
||||
<div>
|
||||
<small class=\\"text-muted\\">
|
||||
Silenced by default/4cf5fd82-1edd-4169-99d1-ff8415e72179
|
||||
</small>
|
||||
</div>
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`<Silence /> matches snapshot when data is present in alertStore 1`] = `
|
||||
"
|
||||
<div class=\\"card mt-1 border-0 p-1\\">
|
||||
<div class=\\"card-text mb-0\\">
|
||||
<span class=\\"text-muted my-1\\">
|
||||
Fake silence
|
||||
<span class=\\"blockquote-footer pt-1\\">
|
||||
<a class=\\"float-right cursor-pointer\\">
|
||||
<svg aria-hidden=\\"true\\"
|
||||
data-prefix=\\"fas\\"
|
||||
data-icon=\\"chevron-up\\"
|
||||
class=\\"svg-inline--fa fa-chevron-up fa-w-14 \\"
|
||||
role=\\"img\\"
|
||||
xmlns=\\"http://www.w3.org/2000/svg\\"
|
||||
viewbox=\\"0 0 448 512\\"
|
||||
>
|
||||
<path fill=\\"currentColor\\"
|
||||
d=\\"M240.971 130.524l194.343 194.343c9.373 9.373 9.373 24.569 0 33.941l-22.667 22.667c-9.357 9.357-24.522 9.375-33.901.04L224 227.495 69.255 381.516c-9.379 9.335-24.544 9.317-33.901-.04l-22.667-22.667c-9.373-9.373-9.373-24.569 0-33.941L207.03 130.525c9.372-9.373 24.568-9.373 33.941-.001z\\"
|
||||
>
|
||||
</path>
|
||||
</svg>
|
||||
</a>
|
||||
<cite class=\\"components-grid-alertgroup-silences mr-2\\">
|
||||
me@example.com
|
||||
</cite>
|
||||
<span class=\\"badge badge-light nmb-05 text-nowrap text-truncate mw-100 align-bottom\\">
|
||||
Expires
|
||||
<time datetime=\\"946756800000\\">
|
||||
in 5 hours
|
||||
</time>
|
||||
<div class=\\"progress silence-progress bg-white\\">
|
||||
<div class=\\"progress-bar bg-success\\"
|
||||
role=\\"progressbar\\"
|
||||
style=\\"width: 50%;\\"
|
||||
aria-valuenow=\\"50\\"
|
||||
aria-valuemin=\\"0\\"
|
||||
aria-valuemax=\\"100\\"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`<Silence /> matches snapshot with expaned details 1`] = `
|
||||
"
|
||||
<div class=\\"card mt-1 border-0 p-1\\">
|
||||
<div class=\\"card-text mb-0\\">
|
||||
<span class=\\"text-muted my-1\\">
|
||||
Fake silence
|
||||
<span class=\\"blockquote-footer pt-1\\">
|
||||
<a class=\\"float-right cursor-pointer\\">
|
||||
<svg aria-hidden=\\"true\\"
|
||||
data-prefix=\\"fas\\"
|
||||
data-icon=\\"chevron-down\\"
|
||||
class=\\"svg-inline--fa fa-chevron-down fa-w-14 \\"
|
||||
role=\\"img\\"
|
||||
xmlns=\\"http://www.w3.org/2000/svg\\"
|
||||
viewbox=\\"0 0 448 512\\"
|
||||
>
|
||||
<path fill=\\"currentColor\\"
|
||||
d=\\"M207.029 381.476L12.686 187.132c-9.373-9.373-9.373-24.569 0-33.941l22.667-22.667c9.357-9.357 24.522-9.375 33.901-.04L224 284.505l154.745-154.021c9.379-9.335 24.544-9.317 33.901.04l22.667 22.667c9.373 9.373 9.373 24.569 0 33.941L240.971 381.476c-9.373 9.372-24.569 9.372-33.942 0z\\"
|
||||
>
|
||||
</path>
|
||||
</svg>
|
||||
</a>
|
||||
<cite class=\\"components-grid-alertgroup-silences mr-2\\">
|
||||
me@example.com
|
||||
</cite>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class=\\"mt-1\\">
|
||||
<span class=\\"components-label components-label-with-hover text-nowrap text-truncate badge badge-warning mw-100\\">
|
||||
@alertmanager: default
|
||||
</span>
|
||||
<a class=\\"badge badge-secondary text-nowrap text-truncate px-1 mr-1\\"
|
||||
href=\\"file:///mock/#/silences/4cf5fd82-1edd-4169-99d1-ff8415e72179\\"
|
||||
target=\\"_blank\\"
|
||||
rel=\\"noopener noreferrer\\"
|
||||
>
|
||||
4cf5fd82-1edd-4169-99d1-ff8415e72179
|
||||
</a>
|
||||
<span class=\\"badge badge-secondary text-nowrap text-truncate px-1 mr-1\\">
|
||||
Silenced
|
||||
<time datetime=\\"946720800000\\">
|
||||
5 hours ago
|
||||
</time>
|
||||
</span>
|
||||
<span class=\\"badge badge-secondary text-nowrap text-truncate px-1 mr-1\\">
|
||||
Expires
|
||||
<time datetime=\\"946756800000\\">
|
||||
in 5 hours
|
||||
</time>
|
||||
</span>
|
||||
<span class=\\"badge badge-success text-nowrap text-truncate px-1 mr-1\\">
|
||||
alertname=MockAlert
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
"
|
||||
`;
|
||||
@@ -14,7 +14,7 @@ import { faExternalLinkAlt } from "@fortawesome/free-solid-svg-icons/faExternalL
|
||||
import { faChevronUp } from "@fortawesome/free-solid-svg-icons/faChevronUp";
|
||||
import { faChevronDown } from "@fortawesome/free-solid-svg-icons/faChevronDown";
|
||||
|
||||
import { StaticLabels } from "Common/Query";
|
||||
import { StaticLabels, QueryOperators } from "Common/Query";
|
||||
import { FilteringLabel } from "Components/Labels/FilteringLabel";
|
||||
|
||||
import "./index.css";
|
||||
@@ -114,7 +114,7 @@ const SilenceDetails = ({ alertmanager, silence }) => {
|
||||
className="badge badge-success text-nowrap text-truncate px-1 mr-1"
|
||||
>
|
||||
{matcher.name}
|
||||
{matcher.isRegex ? "=~" : "="}
|
||||
{matcher.isRegex ? QueryOperators.Regex : QueryOperators.Equal}
|
||||
{matcher.value}
|
||||
</span>
|
||||
))}
|
||||
@@ -228,4 +228,4 @@ const Silence = inject("alertStore")(
|
||||
)
|
||||
);
|
||||
|
||||
export { Silence };
|
||||
export { Silence, SilenceDetails, SilenceExpiryBadgeWithProgress };
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
import React from "react";
|
||||
|
||||
import { Provider } from "mobx-react";
|
||||
|
||||
import { mount, shallow } from "enzyme";
|
||||
|
||||
import toDiffableHtml from "diffable-html";
|
||||
|
||||
import { advanceTo, clear } from "jest-date-mock";
|
||||
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { Silence, SilenceDetails, SilenceExpiryBadgeWithProgress } from ".";
|
||||
|
||||
const mockAfterUpdate = jest.fn();
|
||||
|
||||
const alertmanager = {
|
||||
name: "default",
|
||||
uri: "file:///mock",
|
||||
state: "suppressed",
|
||||
startsAt: "2000-01-01T10:00:00Z",
|
||||
endsAt: "0001-01-01T00:00:00Z",
|
||||
source: "localhost/prometheus",
|
||||
silencedBy: ["4cf5fd82-1edd-4169-99d1-ff8415e72179"]
|
||||
};
|
||||
|
||||
const silence = {
|
||||
id: "4cf5fd82-1edd-4169-99d1-ff8415e72179",
|
||||
matchers: [
|
||||
{
|
||||
name: "alertname",
|
||||
value: "MockAlert",
|
||||
isRegex: false
|
||||
}
|
||||
],
|
||||
startsAt: "2000-01-01T10:00:00Z",
|
||||
endsAt: "2000-01-01T20:00:00Z",
|
||||
createdAt: "0001-01-01T00:00:00Z",
|
||||
createdBy: "me@example.com",
|
||||
comment: "Fake silence",
|
||||
jiraID: "",
|
||||
jiraURL: ""
|
||||
};
|
||||
|
||||
let alertStore;
|
||||
|
||||
beforeEach(() => {
|
||||
advanceTo(new Date(2000, 0, 1, 15, 0, 0));
|
||||
alertStore = new AlertStore([]);
|
||||
alertStore.data.upstreams = {
|
||||
counters: {
|
||||
total: 1,
|
||||
healthy: 1,
|
||||
failed: 0
|
||||
},
|
||||
instances: [
|
||||
{
|
||||
name: "default",
|
||||
uri: "file:///mock",
|
||||
error: ""
|
||||
}
|
||||
]
|
||||
};
|
||||
alertStore.data.silences = {
|
||||
default: {
|
||||
"4cf5fd82-1edd-4169-99d1-ff8415e72179": silence
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// reset Date() to current time
|
||||
clear();
|
||||
});
|
||||
|
||||
const MountedSilence = () => {
|
||||
return mount(
|
||||
<Provider alertStore={alertStore}>
|
||||
<Silence
|
||||
alertStore={alertStore}
|
||||
alertmanager={alertmanager}
|
||||
silenceID="4cf5fd82-1edd-4169-99d1-ff8415e72179"
|
||||
afterUpdate={mockAfterUpdate}
|
||||
/>
|
||||
</Provider>
|
||||
);
|
||||
};
|
||||
|
||||
describe("<Silence />", () => {
|
||||
it("matches snapshot when data is present in alertStore", () => {
|
||||
const tree = MountedSilence().find("Silence");
|
||||
expect(toDiffableHtml(tree.html())).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders full silence when data is present in alertStore", () => {
|
||||
const tree = MountedSilence().find("Silence");
|
||||
const fallback = tree.find("FallbackSilenceDesciption");
|
||||
expect(fallback).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("matches snapshot when data is not present in alertStore", () => {
|
||||
alertStore.data.silences = {};
|
||||
const tree = MountedSilence().find("Silence");
|
||||
expect(toDiffableHtml(tree.html())).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders FallbackSilenceDesciption when Alertmanager data is not present in alertStore", () => {
|
||||
alertStore.data.silences = {};
|
||||
const tree = MountedSilence();
|
||||
const fallback = tree.find("FallbackSilenceDesciption");
|
||||
expect(fallback).toHaveLength(1);
|
||||
expect(tree.text()).toBe(
|
||||
"Silenced by default/4cf5fd82-1edd-4169-99d1-ff8415e72179"
|
||||
);
|
||||
});
|
||||
|
||||
it("renders FallbackSilenceDesciption when silence data is not present in alertStore", () => {
|
||||
alertStore.data.silences.default = {};
|
||||
const tree = MountedSilence();
|
||||
const fallback = tree.find("FallbackSilenceDesciption");
|
||||
expect(fallback).toHaveLength(1);
|
||||
expect(tree.text()).toBe(
|
||||
"Silenced by default/4cf5fd82-1edd-4169-99d1-ff8415e72179"
|
||||
);
|
||||
});
|
||||
|
||||
it("clicking on expand toggle shows silence details", () => {
|
||||
const tree = MountedSilence();
|
||||
const toggle = tree.find("a.float-right.cursor-pointer");
|
||||
toggle.simulate("click");
|
||||
const details = tree.find("SilenceDetails");
|
||||
expect(details).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("matches snapshot with expaned details", () => {
|
||||
const tree = MountedSilence().find("Silence");
|
||||
tree.instance().collapse.toggle();
|
||||
expect(toDiffableHtml(tree.html())).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders comment as link when jiraURL is set", () => {
|
||||
alertStore.data.silences.default[silence.id].jiraURL =
|
||||
"http://jira.example.com";
|
||||
const tree = MountedSilence().find("Silence");
|
||||
const link = tree.find("a[href='http://jira.example.com']");
|
||||
expect(link).toHaveLength(1);
|
||||
expect(link.text()).toBe("Fake silence");
|
||||
});
|
||||
});
|
||||
|
||||
const ShallowSilenceDetails = () => {
|
||||
return shallow(
|
||||
<SilenceDetails alertmanager={alertmanager} silence={silence} />
|
||||
);
|
||||
};
|
||||
|
||||
describe("<SilenceDetails />", () => {
|
||||
it("unexpired silence endsAt label uses 'secondary' class", () => {
|
||||
const tree = ShallowSilenceDetails();
|
||||
const endsAt = tree.find("span.badge").at(1);
|
||||
expect(endsAt.html()).toMatch(/badge-secondary/);
|
||||
});
|
||||
|
||||
it("expired silence endsAt label uses 'danger' class", () => {
|
||||
advanceTo(new Date(2000, 0, 1, 23, 0, 0));
|
||||
const tree = ShallowSilenceDetails();
|
||||
const endsAt = tree.find("span.badge").at(1);
|
||||
expect(endsAt.html()).toMatch(/badge-danger/);
|
||||
});
|
||||
});
|
||||
|
||||
const ShallowSilenceExpiryBadgeWithProgress = () => {
|
||||
return shallow(<SilenceExpiryBadgeWithProgress silence={silence} />);
|
||||
};
|
||||
|
||||
describe("<SilenceExpiryBadgeWithProgress />", () => {
|
||||
it("renders with class 'danger' and no progressbar when expired", () => {
|
||||
advanceTo(new Date(2001, 0, 1, 23, 0, 0));
|
||||
const tree = ShallowSilenceExpiryBadgeWithProgress();
|
||||
expect(tree.html()).toMatch(/badge-danger/);
|
||||
expect(tree.text()).toBe("Expired <t />");
|
||||
});
|
||||
|
||||
it("progressbar uses class 'danger' when > 90%", () => {
|
||||
advanceTo(new Date(2000, 0, 1, 19, 30, 0));
|
||||
const tree = ShallowSilenceExpiryBadgeWithProgress();
|
||||
expect(tree.html()).toMatch(/progress-bar bg-danger/);
|
||||
});
|
||||
|
||||
it("progressbar uses class 'danger' when > 75%", () => {
|
||||
advanceTo(new Date(2000, 0, 1, 17, 45, 0));
|
||||
const tree = ShallowSilenceExpiryBadgeWithProgress();
|
||||
expect(tree.html()).toMatch(/progress-bar bg-warning/);
|
||||
});
|
||||
});
|
||||
173
ui/src/Components/Grid/AlertGrid/AlertGroup/index.test.js
Normal file
173
ui/src/Components/Grid/AlertGrid/AlertGroup/index.test.js
Normal file
@@ -0,0 +1,173 @@
|
||||
import React from "react";
|
||||
|
||||
import { Provider } from "mobx-react";
|
||||
|
||||
import { mount } from "enzyme";
|
||||
|
||||
import moment from "moment";
|
||||
|
||||
import { MockAlert, MockAlertGroup } from "__mocks__/Alerts.js";
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { Settings } from "Stores/Settings";
|
||||
import { SilenceFormStore } from "Stores/SilenceFormStore";
|
||||
import { AlertGroup } from ".";
|
||||
|
||||
let alertStore;
|
||||
let settingsStore;
|
||||
let silenceFormStore;
|
||||
let group;
|
||||
|
||||
const MockGroup = (groupName, alertCount) => {
|
||||
const group = MockAlertGroup(
|
||||
{ alertname: "Fake Alert", group: groupName },
|
||||
[],
|
||||
[],
|
||||
{}
|
||||
);
|
||||
return group;
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
alertStore = new AlertStore([]);
|
||||
settingsStore = new Settings();
|
||||
silenceFormStore = new SilenceFormStore();
|
||||
group = MockGroup();
|
||||
});
|
||||
|
||||
const MockAlerts = alertCount => {
|
||||
for (let i = 1; i <= alertCount; i++) {
|
||||
let alert = MockAlert([], { instance: `instance${i}` });
|
||||
const startsAt = moment().toISOString();
|
||||
alert.startsAt = startsAt;
|
||||
alert.alertmanager[0].startsAt = startsAt;
|
||||
group.alerts.push(alert);
|
||||
}
|
||||
};
|
||||
|
||||
const MountedAlertGroup = (afterUpdate, showAlertmanagers) => {
|
||||
return mount(
|
||||
<Provider alertStore={alertStore}>
|
||||
<AlertGroup
|
||||
afterUpdate={afterUpdate}
|
||||
group={group}
|
||||
showAlertmanagers={showAlertmanagers}
|
||||
settingsStore={settingsStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
/>
|
||||
</Provider>
|
||||
);
|
||||
};
|
||||
|
||||
describe("<AlertGroup />", () => {
|
||||
it("renders Alertmanager labels in footer if showAlertmanagersInFooter=true", () => {
|
||||
MockAlerts(2);
|
||||
const tree = MountedAlertGroup(jest.fn(), true).find("AlertGroup");
|
||||
expect(tree.find("GroupFooter").html()).toMatch(/@alertmanager: default/);
|
||||
});
|
||||
|
||||
it("only renders titlebar when collapsed", () => {
|
||||
MockAlerts(10);
|
||||
const tree = MountedAlertGroup(jest.fn(), false);
|
||||
const alertGroup = tree.find("AlertGroup");
|
||||
alertGroup.instance().collapse.toggle();
|
||||
expect(alertGroup.instance().collapse.value).toBe(true);
|
||||
tree.update();
|
||||
expect(tree.find("Alert")).toHaveLength(0);
|
||||
expect(tree.find("ul.list-group")).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
const ValidateLoadButtonPresent = (totalAlerts, isPresent) => {
|
||||
MockAlerts(totalAlerts);
|
||||
const tree = MountedAlertGroup(jest.fn(), false);
|
||||
const buttons = tree.find("button");
|
||||
expect(buttons).toHaveLength(isPresent ? 2 : 0);
|
||||
};
|
||||
|
||||
const ValidateLoadButtonAction = (
|
||||
totalAlerts,
|
||||
buttonIndex,
|
||||
iconMatch,
|
||||
loadedAlerts,
|
||||
alertsToRenderBeforeClick
|
||||
) => {
|
||||
MockAlerts(totalAlerts);
|
||||
const tree = MountedAlertGroup(jest.fn(), false);
|
||||
if (alertsToRenderBeforeClick !== undefined) {
|
||||
tree
|
||||
.find("AlertGroup")
|
||||
.instance().renderConfig.alertsToRender = alertsToRenderBeforeClick;
|
||||
tree.update();
|
||||
}
|
||||
const loadMore = tree.find("button").at(buttonIndex);
|
||||
expect(loadMore.html()).toMatch(iconMatch);
|
||||
loadMore.simulate("click");
|
||||
tree.update();
|
||||
expect(tree.find("Alert")).toHaveLength(loadedAlerts);
|
||||
};
|
||||
|
||||
describe("<AlertGroup /> renderConfig", () => {
|
||||
it("settingsStore.alertGroupConfig.config.defaultRenderCount should be 5 by default", () => {
|
||||
expect(settingsStore.alertGroupConfig.config.defaultRenderCount).toBe(5);
|
||||
});
|
||||
|
||||
it("renderConfig.alertsToRender should be 5 by default", () => {
|
||||
const tree = MountedAlertGroup(jest.fn(), false).find("AlertGroup");
|
||||
expect(tree.instance().renderConfig.alertsToRender).toBe(5);
|
||||
});
|
||||
|
||||
it("renders only up to renderConfig.alertsToRender alerts", () => {
|
||||
MockAlerts(50);
|
||||
const tree = MountedAlertGroup(jest.fn(), false).find("AlertGroup");
|
||||
const alerts = tree.find("Alert");
|
||||
expect(alerts).toHaveLength(tree.instance().renderConfig.alertsToRender);
|
||||
});
|
||||
|
||||
it("load buttons are not rendered for 1 alert", () => {
|
||||
ValidateLoadButtonPresent(1, false);
|
||||
});
|
||||
|
||||
it("load buttons are not rendered for 5 alerts", () => {
|
||||
ValidateLoadButtonPresent(5, false);
|
||||
});
|
||||
|
||||
it("load buttons are rendered for 6 alert", () => {
|
||||
ValidateLoadButtonPresent(6, true);
|
||||
});
|
||||
|
||||
it("clicking - icon hides 1 alert if there's 6 in total", () => {
|
||||
ValidateLoadButtonAction(6, 0, /fa-minus/, 5, 6);
|
||||
});
|
||||
|
||||
it("clicking - icon hides 1 alert if there's 6 in total and we're showing 3", () => {
|
||||
ValidateLoadButtonAction(6, 0, /fa-minus/, 2, 3);
|
||||
});
|
||||
|
||||
it("clicking - icon hides 2 alerts if there's 7 in total and we're showing 7", () => {
|
||||
ValidateLoadButtonAction(7, 0, /fa-minus/, 5, 7);
|
||||
});
|
||||
|
||||
it("clicking - icon hides 5 alerts if there's 10 in total and we're showing 10", () => {
|
||||
ValidateLoadButtonAction(10, 0, /fa-minus/, 5, 10);
|
||||
});
|
||||
|
||||
it("clicking - icon hides 5 alerts if there's 18 in total and we're showing 17", () => {
|
||||
ValidateLoadButtonAction(18, 0, /fa-minus/, 12, 17);
|
||||
});
|
||||
|
||||
it("clicking + icon loads 1 more alert if there's 6 in total", () => {
|
||||
ValidateLoadButtonAction(6, 1, /fa-plus/, 6);
|
||||
});
|
||||
|
||||
it("clicking + icon loads 4 more alert if there's 9 in total", () => {
|
||||
ValidateLoadButtonAction(9, 1, /fa-plus/, 9);
|
||||
});
|
||||
|
||||
it("clicking + icon loads 5 more alert if there's 14 in total", () => {
|
||||
ValidateLoadButtonAction(14, 1, /fa-plus/, 10);
|
||||
});
|
||||
|
||||
it("clicking + icon loads 5 more alert if there's 25 in total and we're showing 16", () => {
|
||||
ValidateLoadButtonAction(25, 1, /fa-plus/, 22, 17);
|
||||
});
|
||||
});
|
||||
@@ -82,9 +82,7 @@ const AlertGrid = observer(
|
||||
ref={this.storeMasonryRef}
|
||||
pack={true}
|
||||
sizes={GridSizesConfig}
|
||||
loadMore={() => {
|
||||
this.loadMore();
|
||||
}}
|
||||
loadMore={this.loadMore}
|
||||
hasMore={
|
||||
this.groupsToRender.value <
|
||||
Object.keys(alertStore.data.groups).length
|
||||
|
||||
98
ui/src/Components/Grid/AlertGrid/index.test.js
Normal file
98
ui/src/Components/Grid/AlertGrid/index.test.js
Normal file
@@ -0,0 +1,98 @@
|
||||
import React from "react";
|
||||
|
||||
import { shallow } from "enzyme";
|
||||
|
||||
import { MockAlert, MockAlertGroup } from "__mocks__/Alerts.js";
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { Settings } from "Stores/Settings";
|
||||
import { SilenceFormStore } from "Stores/SilenceFormStore";
|
||||
import { AlertGrid } from ".";
|
||||
|
||||
let alertStore;
|
||||
let settingsStore;
|
||||
let silenceFormStore;
|
||||
|
||||
beforeEach(() => {
|
||||
alertStore = new AlertStore([]);
|
||||
settingsStore = new Settings();
|
||||
silenceFormStore = new SilenceFormStore();
|
||||
});
|
||||
|
||||
const ShallowAlertGrid = () => {
|
||||
return shallow(
|
||||
<AlertGrid
|
||||
alertStore={alertStore}
|
||||
settingsStore={settingsStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const MockGroup = (groupName, alertCount) => {
|
||||
let alerts = [];
|
||||
for (let i = 1; i <= alertCount; i++) {
|
||||
alerts.push(MockAlert([], { instance: `instance${i}` }));
|
||||
}
|
||||
const group = MockAlertGroup(
|
||||
{ alertname: "Fake Alert", group: groupName },
|
||||
alerts,
|
||||
[],
|
||||
{}
|
||||
);
|
||||
return group;
|
||||
};
|
||||
|
||||
const MockGroupList = count => {
|
||||
let groups = {};
|
||||
for (let i = 1; i <= count; i++) {
|
||||
let id = `id${i}`;
|
||||
let hash = `hash${i}`;
|
||||
let group = MockGroup(`group${i}`, count);
|
||||
group.id = id;
|
||||
group.hash = hash;
|
||||
groups[id] = group;
|
||||
}
|
||||
alertStore.data.upstreams = {
|
||||
counters: { total: 0, healthy: 1, failed: 0 },
|
||||
instances: [{ name: "am", uri: "http://am", error: "" }]
|
||||
};
|
||||
alertStore.data.groups = groups;
|
||||
};
|
||||
|
||||
describe("<AlertGrid />", () => {
|
||||
it("renders only first 50 alert groups", () => {
|
||||
MockGroupList(60);
|
||||
const tree = ShallowAlertGrid();
|
||||
const alertGroups = tree.find("AlertGroup");
|
||||
expect(alertGroups).toHaveLength(50);
|
||||
});
|
||||
|
||||
it("appends 30 groups after loadMore() call", () => {
|
||||
MockGroupList(100);
|
||||
const tree = ShallowAlertGrid();
|
||||
// call it directly, it should happen on scroll to the bottom of the page
|
||||
tree.instance().loadMore();
|
||||
const alertGroups = tree.find("AlertGroup");
|
||||
expect(alertGroups).toHaveLength(80);
|
||||
});
|
||||
|
||||
it("calls masonryRepack() after update`", () => {
|
||||
const tree = ShallowAlertGrid();
|
||||
const instance = tree.instance();
|
||||
const repackSpy = jest.spyOn(instance, "masonryRepack");
|
||||
// it's a shallow render so we don't really have masonry mounted, fake it
|
||||
instance.masonryComponentReference.ref = {
|
||||
forcePack: jest.fn()
|
||||
};
|
||||
instance.componentDidUpdate();
|
||||
expect(repackSpy).toHaveBeenCalled();
|
||||
expect(instance.masonryComponentReference.ref.forcePack).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("calling storeMasonryRef() saves the ref in local store", () => {
|
||||
const tree = ShallowAlertGrid();
|
||||
const instance = tree.instance();
|
||||
instance.storeMasonryRef("foo");
|
||||
expect(instance.masonryComponentReference.ref).toBe("foo");
|
||||
});
|
||||
});
|
||||
58
ui/src/Components/Grid/index.test.js
Normal file
58
ui/src/Components/Grid/index.test.js
Normal file
@@ -0,0 +1,58 @@
|
||||
import React from "react";
|
||||
|
||||
import { shallow } from "enzyme";
|
||||
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { Settings } from "Stores/Settings";
|
||||
import { SilenceFormStore } from "Stores/SilenceFormStore";
|
||||
import { Grid } from ".";
|
||||
|
||||
let alertStore;
|
||||
let settingsStore;
|
||||
let silenceFormStore;
|
||||
|
||||
beforeEach(() => {
|
||||
alertStore = new AlertStore([]);
|
||||
settingsStore = new Settings();
|
||||
silenceFormStore = new SilenceFormStore();
|
||||
});
|
||||
|
||||
const ShallowGrid = () => {
|
||||
return shallow(
|
||||
<Grid
|
||||
alertStore={alertStore}
|
||||
settingsStore={settingsStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
describe("<Grid />", () => {
|
||||
it("renders only AlertGrid when all upstreams are healthy", () => {
|
||||
const tree = ShallowGrid();
|
||||
expect(tree.text()).toBe("<AlertGrid />");
|
||||
});
|
||||
|
||||
it("renders UpstreamError for each unhealthy upstream", () => {
|
||||
alertStore.data.upstreams = {
|
||||
counters: { total: 3, healthy: 1, failed: 2 },
|
||||
instances: [
|
||||
{ name: "am1", uri: "http://am1", error: "error 1" },
|
||||
{ name: "am2", uri: "file:///mock", error: "" },
|
||||
{ name: "am3", uri: "http://am1", error: "error 2" }
|
||||
]
|
||||
};
|
||||
const tree = ShallowGrid();
|
||||
expect(tree.text()).toBe("<UpstreamError /><UpstreamError /><AlertGrid />");
|
||||
});
|
||||
|
||||
it("renders only FatalError on failed fetch", () => {
|
||||
alertStore.status.error = "error";
|
||||
alertStore.data.upstreams = {
|
||||
counters: { total: 0, healthy: 0, failed: 1 },
|
||||
instances: [{ name: "am", uri: "http://am1", error: "error" }]
|
||||
};
|
||||
const tree = ShallowGrid();
|
||||
expect(tree.text()).toBe("<FatalError />");
|
||||
});
|
||||
});
|
||||
@@ -16,7 +16,9 @@ SetupRaven(settingsElement);
|
||||
Moment.startPooledTimer();
|
||||
|
||||
const defaultFilters = ParseDefaultFilters(settingsElement);
|
||||
ReactDOM.render(
|
||||
|
||||
// https://wetainment.com/testing-indexjs/
|
||||
export default ReactDOM.render(
|
||||
<App defaultFilters={defaultFilters} />,
|
||||
document.getElementById("root")
|
||||
document.getElementById("root") || document.createElement("div")
|
||||
);
|
||||
|
||||
5
ui/src/index.test.js
Normal file
5
ui/src/index.test.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import Index from "./index.js";
|
||||
|
||||
it("renders without crashing", () => {
|
||||
expect(Index).toBeTruthy();
|
||||
});
|
||||
@@ -15,6 +15,9 @@ require("jest-localstorage-mock");
|
||||
// favico.js needs canvas
|
||||
require("jest-canvas-mock");
|
||||
|
||||
// used to mock current time since we render moment.fromNow() in some places
|
||||
require("jest-date-mock");
|
||||
|
||||
// fetch is used in multiple places to interact with Go backend
|
||||
// or upstream Alertmanager API
|
||||
global.fetch = require("jest-fetch-mock");
|
||||
|
||||
Reference in New Issue
Block a user