From b1a347592d62c517f74166315aaf06f1e1f5f25d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Fri, 31 Aug 2018 21:00:00 +0100 Subject: [PATCH 01/13] feat(tests): add tests for Grid component --- ui/src/Components/Grid/index.test.js | 58 ++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 ui/src/Components/Grid/index.test.js diff --git a/ui/src/Components/Grid/index.test.js b/ui/src/Components/Grid/index.test.js new file mode 100644 index 000000000..d2a23fdcb --- /dev/null +++ b/ui/src/Components/Grid/index.test.js @@ -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( + + ); +}; + +describe("", () => { + it("renders only AlertGrid when all upstreams are healthy", () => { + const tree = ShallowGrid(); + expect(tree.text()).toBe(""); + }); + + 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(""); + }); + + 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(""); + }); +}); From 572f58ea0421db152a59e882ffd5e148a85cb7a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Fri, 31 Aug 2018 21:18:51 +0100 Subject: [PATCH 02/13] fix(ui): no need to wrap this --- ui/src/Components/Grid/AlertGrid/index.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ui/src/Components/Grid/AlertGrid/index.js b/ui/src/Components/Grid/AlertGrid/index.js index 80305e178..6d3cd9c18 100644 --- a/ui/src/Components/Grid/AlertGrid/index.js +++ b/ui/src/Components/Grid/AlertGrid/index.js @@ -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 From 4b9b1556c93bb831a1d3c896b1dd2e248f113cc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Fri, 31 Aug 2018 21:43:06 +0100 Subject: [PATCH 03/13] feat(tests): add AlertGrid test coverage --- .../Components/Grid/AlertGrid/index.test.js | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 ui/src/Components/Grid/AlertGrid/index.test.js diff --git a/ui/src/Components/Grid/AlertGrid/index.test.js b/ui/src/Components/Grid/AlertGrid/index.test.js new file mode 100644 index 000000000..4f3f7f3e6 --- /dev/null +++ b/ui/src/Components/Grid/AlertGrid/index.test.js @@ -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( + + ); +}; + +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("", () => { + 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"); + }); +}); From 879276e0171eed6787e294e59a163eaf9b225b22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Fri, 31 Aug 2018 23:25:05 +0100 Subject: [PATCH 04/13] feat(tests): add AlertGroup tests --- .../Grid/AlertGrid/AlertGroup/index.test.js | 173 ++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 ui/src/Components/Grid/AlertGrid/AlertGroup/index.test.js diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/index.test.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/index.test.js new file mode 100644 index 000000000..9c71d5cac --- /dev/null +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/index.test.js @@ -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( + + + + ); +}; + +describe("", () => { + 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(" 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); + }); +}); From 15500253365f1c6caa1cb3afdba41dff7dd30096 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Sat, 1 Sep 2018 10:17:38 +0100 Subject: [PATCH 05/13] fix(ui): don't hardcode matchers --- ui/src/Components/Grid/AlertGrid/AlertGroup/Silence/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/Silence/index.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/Silence/index.js index 4a5975376..e546a1280 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/Silence/index.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/Silence/index.js @@ -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} ))} From 4dfd044cc39327fef422007fc42aed9805aac813 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Sat, 1 Sep 2018 19:36:01 +0100 Subject: [PATCH 06/13] feat(tests): add Silence component tests --- ui/package-lock.json | 40 ++++ ui/package.json | 2 + .../Silence/__snapshots__/index.test.js.snap | 118 +++++++++++ .../AlertGrid/AlertGroup/Silence/index.js | 2 +- .../AlertGroup/Silence/index.test.js | 194 ++++++++++++++++++ ui/src/setupTests.js | 3 + 6 files changed, 358 insertions(+), 1 deletion(-) create mode 100644 ui/src/Components/Grid/AlertGrid/AlertGroup/Silence/__snapshots__/index.test.js.snap create mode 100644 ui/src/Components/Grid/AlertGrid/AlertGroup/Silence/index.test.js diff --git a/ui/package-lock.json b/ui/package-lock.json index 4e0401444..ace7fd269 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -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", diff --git a/ui/package.json b/ui/package.json index cc5f4caac..3a20f3b8f 100644 --- a/ui/package.json +++ b/ui/package.json @@ -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", diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/Silence/__snapshots__/index.test.js.snap b/ui/src/Components/Grid/AlertGrid/AlertGroup/Silence/__snapshots__/index.test.js.snap new file mode 100644 index 000000000..2c7daac4d --- /dev/null +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/Silence/__snapshots__/index.test.js.snap @@ -0,0 +1,118 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` matches snapshot when data is not present in alertStore 1`] = ` +" +
+ + Silenced by default/4cf5fd82-1edd-4169-99d1-ff8415e72179 + +
+" +`; + +exports[` matches snapshot when data is present in alertStore 1`] = ` +" +
+
+ + Fake silence + + + + + + + + + me@example.com + + + Expires + +
+
+
+
+
+
+
+
+
+" +`; + +exports[` matches snapshot with expaned details 1`] = ` +" +
+
+ + Fake silence + + + + + + + + + me@example.com + + + +
+
+ + @alertmanager: default + + + 4cf5fd82-1edd-4169-99d1-ff8415e72179 + + + Silenced + + + + Expires + + + + alertname=MockAlert + +
+
+" +`; diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/Silence/index.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/Silence/index.js index e546a1280..a265583e9 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/Silence/index.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/Silence/index.js @@ -228,4 +228,4 @@ const Silence = inject("alertStore")( ) ); -export { Silence }; +export { Silence, SilenceDetails, SilenceExpiryBadgeWithProgress }; diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/Silence/index.test.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/Silence/index.test.js new file mode 100644 index 000000000..56ddd651b --- /dev/null +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/Silence/index.test.js @@ -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( + + + + ); +}; + +describe("", () => { + 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( + + ); +}; + +describe("", () => { + 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(); +}; + +describe("", () => { + 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 "); + }); + + 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/); + }); +}); diff --git a/ui/src/setupTests.js b/ui/src/setupTests.js index a18bc9688..da247227d 100644 --- a/ui/src/setupTests.js +++ b/ui/src/setupTests.js @@ -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"); From c6b3bb50002b51860179be55090b9cb124cfac8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Sat, 1 Sep 2018 21:19:48 +0100 Subject: [PATCH 07/13] chore(demo): add extra config to show hidden annotations in demo mode --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index d40b0c01b..7925d47ab 100644 --- a/Makefile +++ b/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" \ From 2dbbef2f3e692f8da573eb0dde9b57c46b4183c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Sat, 1 Sep 2018 21:20:22 +0100 Subject: [PATCH 08/13] fix(ui): UI doesn't need to calculate which annotation is hidden by default, backend provides this information --- .../Grid/AlertGrid/AlertGroup/Alert/index.js | 1 + .../AlertGrid/AlertGroup/Annotation/index.js | 33 ++----------------- .../AlertGrid/AlertGroup/GroupFooter/index.js | 1 + 3 files changed, 5 insertions(+), 30 deletions(-) diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/index.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/index.js index 809d433bd..dd141e368 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/index.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/index.js @@ -50,6 +50,7 @@ const Alert = observer( key={a.name} name={a.name} value={a.value} + visible={a.visible} afterUpdate={afterUpdate} /> ))} diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/Annotation/index.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/Annotation/index.js index 2742cef7e..70eae7ec5 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/Annotation/index.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/Annotation/index.js @@ -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; diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupFooter/index.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupFooter/index.js index 8fa48fef5..9ba34c336 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupFooter/index.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupFooter/index.js @@ -28,6 +28,7 @@ const GroupFooter = observer( key={a.name} name={a.name} value={a.value} + visible={a.visible} afterUpdate={afterUpdate} /> ))} From 1e37a2f130c18d506e09356e3dcc80a1f022e364 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Sat, 1 Sep 2018 21:23:48 +0100 Subject: [PATCH 09/13] feat(tests): add GroupFoote snapshot test --- .../__snapshots__/index.test.js.snap | 79 +++++++++++++++++++ .../AlertGroup/GroupFooter/index.test.js | 54 +++++++++++++ 2 files changed, 133 insertions(+) create mode 100644 ui/src/Components/Grid/AlertGrid/AlertGroup/GroupFooter/__snapshots__/index.test.js.snap create mode 100644 ui/src/Components/Grid/AlertGrid/AlertGroup/GroupFooter/index.test.js diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupFooter/__snapshots__/index.test.js.snap b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupFooter/__snapshots__/index.test.js.snap new file mode 100644 index 000000000..503472efe --- /dev/null +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupFooter/__snapshots__/index.test.js.snap @@ -0,0 +1,79 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` matches snapshot 1`] = ` +" + +" +`; diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupFooter/index.test.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupFooter/index.test.js new file mode 100644 index 000000000..c4d39ea2b --- /dev/null +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupFooter/index.test.js @@ -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( + + + + ); +}; + +describe("", () => { + it("matches snapshot", () => { + const tree = MountedGroupFooter().find("GroupFooter"); + expect(toDiffableHtml(tree.html())).toMatchSnapshot(); + }); +}); From 7e2f1fb446ffa577f2b875a08b8c5e79a8046435 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Sat, 1 Sep 2018 21:59:18 +0100 Subject: [PATCH 10/13] feat(tests): add annotation tests --- .../__snapshots__/index.test.js.snap | 90 ++++++++++++++++ .../AlertGroup/Annotation/index.test.js | 100 ++++++++++++++++++ 2 files changed, 190 insertions(+) create mode 100644 ui/src/Components/Grid/AlertGrid/AlertGroup/Annotation/__snapshots__/index.test.js.snap create mode 100644 ui/src/Components/Grid/AlertGrid/AlertGroup/Annotation/index.test.js diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/Annotation/__snapshots__/index.test.js.snap b/ui/src/Components/Grid/AlertGrid/AlertGroup/Annotation/__snapshots__/index.test.js.snap new file mode 100644 index 000000000..dfaa05fd2 --- /dev/null +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/Annotation/__snapshots__/index.test.js.snap @@ -0,0 +1,90 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` matches snapshot 1`] = ` + + + + annotation name + +`; + +exports[` matches snapshot when visible=false 1`] = ` +" +
+ + + + + foo +
+" +`; + +exports[` matches snapshot when visible=true 1`] = ` +" +
+ + + + + + foo: + + + some long text + +
+" +`; diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/Annotation/index.test.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/Annotation/index.test.js new file mode 100644 index 000000000..ed1edbbad --- /dev/null +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/Annotation/index.test.js @@ -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( + + ); +}; + +describe("", () => { + 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( + + ); +}; + +const MountedNonLinkAnnotation = visible => { + return mount( + + ); +}; + +describe("", () => { + 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/); + }); +}); From 9332c5e4b2f6735a70bf84142c73aa22c878f1cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Sat, 1 Sep 2018 22:58:15 +0100 Subject: [PATCH 11/13] feat(tests): add Alert tests --- .../Alert/__snapshots__/index.test.js.snap | 78 +++++++++++++++++ .../AlertGrid/AlertGroup/Alert/index.test.js | 87 +++++++++++++++++++ 2 files changed, 165 insertions(+) create mode 100644 ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/__snapshots__/index.test.js.snap create mode 100644 ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/index.test.js diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/__snapshots__/index.test.js.snap b/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/__snapshots__/index.test.js.snap new file mode 100644 index 000000000..321615444 --- /dev/null +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/__snapshots__/index.test.js.snap @@ -0,0 +1,78 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` matches snapshot with showAlertmanagers=false showReceiver=false 1`] = ` +" +
  • +
    +
    + + + + + + help: + + + some long text + +
    +
    + + + + + hidden +
    +
    + + + + + job: node_exporter + + + cluster: dev + + + + + + + link + +
  • +" +`; diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/index.test.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/index.test.js new file mode 100644 index 000000000..67f424f33 --- /dev/null +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/index.test.js @@ -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( + + + + ); +}; + +describe("", () => { + 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/); + }); +}); From dc31861bab22847b88b3343d831a4329e9d64ffd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Sat, 1 Sep 2018 23:47:37 +0100 Subject: [PATCH 12/13] feat(tests): add GroupMenu tests --- ui/__mocks__/copy-to-clipboard.js | 6 ++ .../AlertGroup/GroupHeader/GroupMenu.js | 2 +- .../AlertGroup/GroupHeader/GroupMenu.test.js | 81 +++++++++++++++++++ 3 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 ui/__mocks__/copy-to-clipboard.js create mode 100644 ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.test.js diff --git a/ui/__mocks__/copy-to-clipboard.js b/ui/__mocks__/copy-to-clipboard.js new file mode 100644 index 000000000..d30cebfdf --- /dev/null +++ b/ui/__mocks__/copy-to-clipboard.js @@ -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; diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.js index d61f1f72c..b337576f8 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.js @@ -150,4 +150,4 @@ const GroupMenu = observer( } ); -export { GroupMenu }; +export { GroupMenu, MenuContent }; diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.test.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.test.js new file mode 100644 index 000000000..eb8c3a7ff --- /dev/null +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.test.js @@ -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(); +}; + +describe("", () => { + 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( + + ); +}; + +describe("", () => { + 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); + }); +}); From 4ff46b0c3c9d40e38fbe7de16619d4351b3db41f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Sun, 2 Sep 2018 00:10:56 +0100 Subject: [PATCH 13/13] feat(tests): add test coverage for index.js --- ui/src/index.js | 6 ++++-- ui/src/index.test.js | 5 +++++ 2 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 ui/src/index.test.js diff --git a/ui/src/index.js b/ui/src/index.js index 6786560c4..4ba254936 100644 --- a/ui/src/index.js +++ b/ui/src/index.js @@ -16,7 +16,9 @@ SetupRaven(settingsElement); Moment.startPooledTimer(); const defaultFilters = ParseDefaultFilters(settingsElement); -ReactDOM.render( + +// https://wetainment.com/testing-indexjs/ +export default ReactDOM.render( , - document.getElementById("root") + document.getElementById("root") || document.createElement("div") ); diff --git a/ui/src/index.test.js b/ui/src/index.test.js new file mode 100644 index 000000000..be85982c3 --- /dev/null +++ b/ui/src/index.test.js @@ -0,0 +1,5 @@ +import Index from "./index.js"; + +it("renders without crashing", () => { + expect(Index).toBeTruthy(); +});