From fbdea793549a8ccaeea4826f2c81eff444b74ff8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Sun, 2 Sep 2018 16:45:40 +0100 Subject: [PATCH 01/17] fix(tests): add test coverage for missed branches in AppBoot --- ui/src/AppBoot.test.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ui/src/AppBoot.test.js b/ui/src/AppBoot.test.js index 705301c97..0f390bf58 100644 --- a/ui/src/AppBoot.test.js +++ b/ui/src/AppBoot.test.js @@ -87,4 +87,14 @@ describe("ParseDefaultFilters()", () => { expect(filters).toContain("foo=bar"); expect(filters).toContain("bar=~baz"); }); + + it("returns [] on filters attr that decodes to an object instead of an array", () => { + const filters = FiltersSetting({ foo: "bar" }); + expect(filters).toHaveLength(0); + }); + + it("returns [] on filters attr that decodes to a string instead of an array", () => { + const filters = FiltersSetting("foo=bar"); + expect(filters).toHaveLength(0); + }); }); From ba6ed7f9ff2a1d1e0608f752a901382156478901 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Sun, 2 Sep 2018 16:50:50 +0100 Subject: [PATCH 02/17] fix(tests): add test coverage for missed branches in AlertGrid --- ui/src/Components/Grid/AlertGrid/index.test.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ui/src/Components/Grid/AlertGrid/index.test.js b/ui/src/Components/Grid/AlertGrid/index.test.js index 4f3f7f3e6..8e7e5dd6e 100644 --- a/ui/src/Components/Grid/AlertGrid/index.test.js +++ b/ui/src/Components/Grid/AlertGrid/index.test.js @@ -89,6 +89,15 @@ describe("", () => { expect(instance.masonryComponentReference.ref.forcePack).toHaveBeenCalled(); }); + it("masonryRepack() doesn't crash when masonryComponentReference.ref=false`", () => { + const tree = ShallowAlertGrid(); + const instance = tree.instance(); + const repackSpy = jest.spyOn(instance, "masonryRepack"); + instance.masonryComponentReference.ref = false; + instance.componentDidUpdate(); + expect(repackSpy).toHaveBeenCalled(); + }); + it("calling storeMasonryRef() saves the ref in local store", () => { const tree = ShallowAlertGrid(); const instance = tree.instance(); From 2bb0f2ffb09f63275a409f7592821876b88d62b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Sun, 2 Sep 2018 17:49:43 +0100 Subject: [PATCH 03/17] fix(ui): fix the logic for deciding when to show @alertmanager labels in the footer, add a test --- .../Grid/AlertGrid/AlertGroup/index.js | 13 +++++++++--- .../Grid/AlertGrid/AlertGroup/index.test.js | 21 +++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/index.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/index.js index 815ddf9d5..a85aed849 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/index.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/index.js @@ -32,6 +32,15 @@ LoadButton.propTypes = { action: PropTypes.func.isRequired }; +const AllAlertsAreUsingSameAlertmanagers = alerts => { + const usedAMs = alerts.map(alert => + alert.alertmanager.map(am => am.name).sort() + ); + return usedAMs.every( + listOfAMs => JSON.stringify(listOfAMs) === JSON.stringify(usedAMs[0]) + ); +}; + const AlertGroup = observer( class AlertGroup extends Component { static propTypes = { @@ -137,9 +146,7 @@ const AlertGroup = observer( // alertmanagers (and there's > 1 alert to show, there's no footer for 1) showAlertmanagersInFooter = group.alerts.length > 1 && - Object.values(group.alertmanagerCount).every( - elem => elem === Object.values(group.alertmanagerCount)[0] - ); + AllAlertsAreUsingSameAlertmanagers(group.alerts); if (showAlertmanagersInFooter) { footerAlertmanagers = group.alerts[0].alertmanager.map(am => am.name); } diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/index.test.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/index.test.js index 9c71d5cac..3739ac412 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/index.test.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/index.test.js @@ -65,6 +65,27 @@ describe("", () => { expect(tree.find("GroupFooter").html()).toMatch(/@alertmanager: default/); }); + it("doesn't render alertmanager labels in footer when they are unique", () => { + MockAlerts(5); + for (let i = 0; i < group.alerts.length; i++) { + group.alerts[i].alertmanager[0].name = `fakeAlertmanager${i}`; + } + group.alertmanagerCount = { + fakeAlertmanager0: 1, + fakeAlertmanager1: 1, + fakeAlertmanager2: 1, + fakeAlertmanager3: 1, + fakeAlertmanager4: 1 + }; + const tree = MountedAlertGroup(jest.fn(), true); + + const alerts = tree.find("ul.list-group"); + expect(alerts.html()).toMatch(/@alertmanager:/); + + const footer = tree.find("GroupFooter"); + expect(footer.html()).not.toMatch(/@alertmanager:/); + }); + it("only renders titlebar when collapsed", () => { MockAlerts(10); const tree = MountedAlertGroup(jest.fn(), false); From a95b919872138837cc7726afe0c1bb563c352c90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Sun, 2 Sep 2018 17:59:51 +0100 Subject: [PATCH 04/17] fix(tests): add missing linkify test coverage --- .../AlertGroup/Annotation/index.test.js | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/Annotation/index.test.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/Annotation/index.test.js index ed1edbbad..31a3bfa67 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/Annotation/index.test.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/Annotation/index.test.js @@ -59,6 +59,18 @@ const MountedNonLinkAnnotation = visible => { ); }; +const MountedNonLinkAnnotationContainingLink = visible => { + return mount( + + ); +}; + describe("", () => { it("matches snapshot when visible=true", () => { const tree = ShallowNonLinkAnnotation(true); @@ -80,6 +92,12 @@ describe("", () => { expect(tree.html()).not.toMatch(/some long text/); }); + it("links inside annotation are rendered as a.href", () => { + const tree = MountedNonLinkAnnotationContainingLink(true); + const link = tree.find("a[href='http://example.com']"); + expect(link.text()).toBe("http://example.com"); + }); + it("clicking on + icon hides the value", () => { const tree = MountedNonLinkAnnotation(true); expect(tree.html()).toMatch(/fa-search-minus/); @@ -89,6 +107,13 @@ describe("", () => { expect(tree.html()).not.toMatch(/some long text/); }); + it("clicking on a link inside annotation doesn't hide the value", () => { + const tree = MountedNonLinkAnnotationContainingLink(true); + expect(tree.html()).toMatch(/fa-search-minus/); + tree.find("a").simulate("click"); + expect(tree.html()).toMatch(/fa-search-minus/); + }); + it("clicking on - icon shows the value", () => { const tree = MountedNonLinkAnnotation(false); expect(tree.html()).toMatch(/fa-search-plus/); From bd4e549b270d968082278d906a66d44643af24d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Sun, 2 Sep 2018 18:02:58 +0100 Subject: [PATCH 05/17] fix(tests): cover regex matches in silence tests --- .../AlertGroup/Silence/__snapshots__/index.test.js.snap | 3 +++ .../Grid/AlertGrid/AlertGroup/Silence/index.test.js | 5 +++++ 2 files changed, 8 insertions(+) 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 index 2c7daac4d..fe7c57330 100644 --- 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 @@ -112,6 +112,9 @@ exports[` matches snapshot with expaned details 1`] = ` alertname=MockAlert + + instance=~foo[0-9]+ + " diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/Silence/index.test.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/Silence/index.test.js index 56ddd651b..4eb9b29de 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/Silence/index.test.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/Silence/index.test.js @@ -30,6 +30,11 @@ const silence = { name: "alertname", value: "MockAlert", isRegex: false + }, + { + name: "instance", + value: "foo[0-9]+", + isRegex: true } ], startsAt: "2000-01-01T10:00:00Z", From 2ee69f9c0862a4b54f5cddb6fd7de7a8f8c79d3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Sun, 2 Sep 2018 18:09:46 +0100 Subject: [PATCH 06/17] fix(tests): add missing HistoryLabels tests --- .../Labels/HistoryLabel/index.test.js | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 ui/src/Components/Labels/HistoryLabel/index.test.js diff --git a/ui/src/Components/Labels/HistoryLabel/index.test.js b/ui/src/Components/Labels/HistoryLabel/index.test.js new file mode 100644 index 000000000..fb64c67ae --- /dev/null +++ b/ui/src/Components/Labels/HistoryLabel/index.test.js @@ -0,0 +1,34 @@ +import React from "react"; + +import { shallow } from "enzyme"; + +import { AlertStore } from "Stores/AlertStore"; + +import { HistoryLabel } from "."; + +let alertStore; + +beforeEach(() => { + alertStore = new AlertStore([]); +}); + +describe("", () => { + it("renders name, matcher and value if all are set", () => { + const tree = shallow( + + ); + expect(tree.text()).toBe("foo=bar"); + }); + + it("renders only value if name is falsey", () => { + const tree = shallow( + + ); + expect(tree.text()).toBe("bar"); + }); +}); From 5e0c766f4477fc856c041918698b61dcf5a61c19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Sun, 2 Sep 2018 18:14:00 +0100 Subject: [PATCH 07/17] fix(tests): use diffable html instead tree snapshots --- .../__snapshots__/index.test.js.snap | 1222 ++++------------- ui/src/Components/MultiSelect/index.test.js | 12 +- 2 files changed, 281 insertions(+), 953 deletions(-) diff --git a/ui/src/Components/MultiSelect/__snapshots__/index.test.js.snap b/ui/src/Components/MultiSelect/__snapshots__/index.test.js.snap index 318800f69..edee4f5ad 100644 --- a/ui/src/Components/MultiSelect/__snapshots__/index.test.js.snap +++ b/ui/src/Components/MultiSelect/__snapshots__/index.test.js.snap @@ -1,968 +1,294 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[` matches snapshot when focused 1`] = ` - - + - - -
- -
- -
-
- -
- -
- - -
- - - - - - -
-
- - +
- - -
-
-
+ + + +
+ + +
+ + + + +
+
+ + +" `; exports[` matches snapshot with a value 1`] = ` - +" +
+
+
+
+ foo +
+
+
+ +
+
+
+
+
+
+ + +
+ + + + +
+
+
+
+" `; exports[` matches snapshot with defaults 1`] = ` - +" +
+
+
+
+ Select... +
+
+
+ +
+
+
+
+
+
+ + +
+ + + + +
+
+
+
+" `; exports[` matches snapshot with isMulti=true 1`] = ` - +" +
+
+
+
+ Select... +
+
+
+ +
+
+
+
+
+
+ + +
+ + + + +
+
+
+
+" `; exports[` matches snapshot with isMulti=true and a value 1`] = ` - +" +
+
+
+
+
+ foo +
+
+ + + + +
+
+
+
+ +
+
+
+
+
+
+
+ + + + +
+ + +
+ + + + +
+
+
+
+" `; diff --git a/ui/src/Components/MultiSelect/index.test.js b/ui/src/Components/MultiSelect/index.test.js index b575edef4..a60fe54d5 100644 --- a/ui/src/Components/MultiSelect/index.test.js +++ b/ui/src/Components/MultiSelect/index.test.js @@ -2,6 +2,8 @@ import React from "react"; import { shallow, mount } from "enzyme"; +import toDiffableHtml from "diffable-html"; + import { MultiSelect } from "."; const Option = value => ({ label: value, value: value }); @@ -25,19 +27,19 @@ class CustomMultiSelect extends MultiSelect { describe("", () => { it("matches snapshot with defaults", () => { const tree = shallow(); - expect(tree).toMatchSnapshot(); + expect(toDiffableHtml(tree.html())).toMatchSnapshot(); }); it("matches snapshot with isMulti=true", () => { const tree = shallow(); - expect(tree).toMatchSnapshot(); + expect(toDiffableHtml(tree.html())).toMatchSnapshot(); }); it("matches snapshot when focused", () => { // this test is to cover styles state.isFocused conditions const tree = mount(); tree.find("input").simulate("focus"); - expect(tree).toMatchSnapshot(); + expect(toDiffableHtml(tree.html())).toMatchSnapshot(); }); it("matches snapshot with a value", () => { @@ -47,7 +49,7 @@ describe("", () => { options={[Option("foo", Option("bar"))]} /> ); - expect(tree).toMatchSnapshot(); + expect(toDiffableHtml(tree.html())).toMatchSnapshot(); }); it("matches snapshot with isMulti=true and a value", () => { @@ -58,6 +60,6 @@ describe("", () => { options={[Option("foo", Option("bar"))]} /> ); - expect(tree).toMatchSnapshot(); + expect(toDiffableHtml(tree.html())).toMatchSnapshot(); }); }); From bbbcb497a5c4f3322a3c6dc96ea586fbe8fca7b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Sun, 2 Sep 2018 18:21:22 +0100 Subject: [PATCH 08/17] fix(tests): format options correctly --- ui/src/Components/MultiSelect/index.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/src/Components/MultiSelect/index.test.js b/ui/src/Components/MultiSelect/index.test.js index a60fe54d5..59b0d236d 100644 --- a/ui/src/Components/MultiSelect/index.test.js +++ b/ui/src/Components/MultiSelect/index.test.js @@ -46,7 +46,7 @@ describe("", () => { const tree = shallow( ); expect(toDiffableHtml(tree.html())).toMatchSnapshot(); @@ -57,7 +57,7 @@ describe("", () => { ); expect(toDiffableHtml(tree.html())).toMatchSnapshot(); From e0d49175664f70f7a831a57e0f108cbef3192b65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Sun, 2 Sep 2018 18:27:36 +0100 Subject: [PATCH 09/17] fix(ui): remove dead code isSelected doesn't seem to get trigger in any way --- ui/src/Components/MultiSelect/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/Components/MultiSelect/index.js b/ui/src/Components/MultiSelect/index.js index d8a528d52..3b4b82dc3 100644 --- a/ui/src/Components/MultiSelect/index.js +++ b/ui/src/Components/MultiSelect/index.js @@ -78,7 +78,7 @@ const ReactSelectStyles = { }), option: (base, state) => ({ ...base, - color: state.isSelected ? "#95a5a6" : "inherit", + color: "inherit", backgroundColor: "inherit", "&:hover": { color: "#fff", backgroundColor: "#455a64", cursor: "pointer" } }) From 96448090543caef2b57358e2202cf72278545003 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Sun, 2 Sep 2018 18:57:44 +0100 Subject: [PATCH 10/17] fix(tests): add missing coverage cases for FilerInput --- .../NavBar/FilterInput/index.test.js | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/ui/src/Components/NavBar/FilterInput/index.test.js b/ui/src/Components/NavBar/FilterInput/index.test.js index 996e56193..296835244 100644 --- a/ui/src/Components/NavBar/FilterInput/index.test.js +++ b/ui/src/Components/NavBar/FilterInput/index.test.js @@ -63,7 +63,16 @@ describe("", () => { ); }); - it("Clicking on form-control div focuses input", () => { + it("submit should be no-op if input value is empty", () => { + const tree = MountedInput(); + const instance = tree.instance(); + instance.inputStore.value = ""; + expect(alertStore.filters.values).toHaveLength(0); + tree.find("form").simulate("submit"); + expect(alertStore.filters.values).toHaveLength(0); + }); + + it("clicking on form-control div focuses input", () => { const tree = MountedInput(); const instance = tree.instance(); const inputSpy = jest.spyOn(instance.inputStore.ref.input, "focus"); @@ -71,6 +80,22 @@ describe("", () => { formControl.simulate("click"); expect(inputSpy).toHaveBeenCalledTimes(1); }); + + it("clicking on a label doesn't trigger input focus", () => { + alertStore.filters.values = [NewUnappliedFilter("foo=bar")]; + const tree = MountedInput(); + const instance = tree.instance(); + const inputSpy = jest.spyOn(instance.inputStore.ref.input, "focus"); + tree.find("FilterInputLabel").simulate("click"); + expect(inputSpy).not.toHaveBeenCalled(); + }); + + it("componentDidMount executes even when inputStore.ref=null", () => { + const tree = MountedInput(); + const instance = tree.instance(); + instance.inputStore.ref = null; + instance.componentDidMount(); + }); }); describe("", () => { @@ -89,6 +114,15 @@ describe("", () => { expect(instance.inputStore.suggestions).toContain("foo=~bar"); }); + it("doesn't fetch any suggestion if the input value is empty", () => { + fetch.mockResponseOnce(JSON.stringify(["foo=bar", "foo=~bar"])); + + const tree = MountedInput(); + const instance = tree.instance(); + instance.onSuggestionsFetchRequested({ value: "" }); + expect(fetch.mock.calls).toHaveLength(0); + }); + it("clicking on a suggestion adds it to filters", async () => { fetch.mockResponse(JSON.stringify(["foo=bar", "foo=~bar"])); From 48f9176cf9593404c47ba9bb90e160f6136a233d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Sun, 2 Sep 2018 19:02:23 +0100 Subject: [PATCH 11/17] fix(tests): add missing coverage cases for AlertManagerInput --- ui/src/Components/SilenceModal/AlertManagerInput.test.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ui/src/Components/SilenceModal/AlertManagerInput.test.js b/ui/src/Components/SilenceModal/AlertManagerInput.test.js index 1e7257fb9..75c42bb1b 100644 --- a/ui/src/Components/SilenceModal/AlertManagerInput.test.js +++ b/ui/src/Components/SilenceModal/AlertManagerInput.test.js @@ -70,6 +70,15 @@ describe("", () => { } }); + it("doesn't override last selected Alertmanager instances on mount", () => { + silenceFormStore.data.alertmanagers = [AlertmanagerOption(1)]; + ShallowAlertManagerInput(); + expect(silenceFormStore.data.alertmanagers).toHaveLength(1); + expect(silenceFormStore.data.alertmanagers).toContainEqual( + AlertmanagerOption(1) + ); + }); + it("renders all 3 suggestions", () => { const tree = ValidateSuggestions(); const options = tree.find("[role='option']"); From b5112880a59ec4cb38328a4b3196cb52a0ffbee2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Sun, 2 Sep 2018 19:12:59 +0100 Subject: [PATCH 12/17] fix(tests): add missing coverage cases for LabelNameInput --- ui/src/Components/SilenceModal/LabelNameInput.test.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/ui/src/Components/SilenceModal/LabelNameInput.test.js b/ui/src/Components/SilenceModal/LabelNameInput.test.js index dbcc8934e..6af967fd4 100644 --- a/ui/src/Components/SilenceModal/LabelNameInput.test.js +++ b/ui/src/Components/SilenceModal/LabelNameInput.test.js @@ -77,7 +77,7 @@ describe("", () => { }, 100); }); - it("suggestions are empited on failed fetch", done => { + it("suggestions are emptied on failed fetch", done => { fetch.mockReject(new Error("fake error message")); ShallowLabelNameInput(); // use timeout since mount will call fetch @@ -86,4 +86,12 @@ describe("", () => { done(); }, 100); }); + + it("doesn't fetch suggestions if value is changed to empty string", () => { + const tree = MountedLabelNameInput(); + const instance = tree.instance(); + const fetchSpy = jest.spyOn(instance, "populateValueSuggestions"); + instance.onChange(""); + expect(fetchSpy).not.toHaveBeenCalled(); + }); }); From 38b0cb15570c8e33ca4f1092250b882be45854d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Sun, 2 Sep 2018 19:21:50 +0100 Subject: [PATCH 13/17] fix(ui): remove dead code --- ui/src/Components/SilenceModal/DateTimeSelect/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/Components/SilenceModal/DateTimeSelect/index.js b/ui/src/Components/SilenceModal/DateTimeSelect/index.js index 650f0192c..e8f902ec8 100644 --- a/ui/src/Components/SilenceModal/DateTimeSelect/index.js +++ b/ui/src/Components/SilenceModal/DateTimeSelect/index.js @@ -114,7 +114,7 @@ const CalculateChangeValueUp = (currentValue, step) => { return 1; } // otherwise use step or a value that moves current value to the next step - return step - (currentValue % step) || step; + return step - (currentValue % step); }; // calculate value for duration decrease button using a goal step From 617895e4716b10c0239363ac912510ac7ff4d69f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Sun, 2 Sep 2018 19:23:50 +0100 Subject: [PATCH 14/17] fix(tests): add missing coverage cases for SilenceFormStore --- ui/src/Stores/SilenceFormStore.test.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ui/src/Stores/SilenceFormStore.test.js b/ui/src/Stores/SilenceFormStore.test.js index bac6d3287..8568e3389 100644 --- a/ui/src/Stores/SilenceFormStore.test.js +++ b/ui/src/Stores/SilenceFormStore.test.js @@ -70,6 +70,11 @@ describe("SilenceFormStore.data", () => { ); }); + it("deleteMatcher() is a no-op when matcher list is empty", () => { + store.data.deleteMatcher(1); + expect(store.data.matchers).toHaveLength(0); + }); + it("fillMatchersFromGroup() creates correct matcher object for a group", () => { const group = MockGroup(); store.data.fillMatchersFromGroup(group); From 852dcfbdc66d43b952615da8998c523ad8076a95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Sun, 2 Sep 2018 19:47:39 +0100 Subject: [PATCH 15/17] fix(ui): compare correct attributes --- ui/src/Stores/AlertStore.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/Stores/AlertStore.js b/ui/src/Stores/AlertStore.js index 21e03792d..f89262e49 100644 --- a/ui/src/Stores/AlertStore.js +++ b/ui/src/Stores/AlertStore.js @@ -280,7 +280,7 @@ class AlertStore { } // settings exported via API - if (!equal(this.settings, result.settings)) { + if (!equal(this.settings.values, result.settings)) { this.settings.values = result.settings; } From 87c8d6e071a91de0c0f9059640d80c9293555cc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Sun, 2 Sep 2018 19:56:31 +0100 Subject: [PATCH 16/17] fix(tests): add missing coverage cases for AlertStore --- ui/src/Stores/AlertStore.js | 7 ++--- ui/src/Stores/AlertStore.test.js | 52 ++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 4 deletions(-) diff --git a/ui/src/Stores/AlertStore.js b/ui/src/Stores/AlertStore.js index f89262e49..130e5ec73 100644 --- a/ui/src/Stores/AlertStore.js +++ b/ui/src/Stores/AlertStore.js @@ -294,10 +294,8 @@ class AlertStore { this.info.totalAlerts = 0; // all unapplied filters should be marked applied to reset progress indicator - for (const [index, filter] of this.filters.values.entries()) { - if (!filter.applied) { - this.filters.values[index].applied = true; - } + for (let i = 0; i < this.filters.values.length; i++) { + this.filters.values[i].applied = true; } return { error: err }; @@ -310,5 +308,6 @@ export { FormatUnseeBackendURI, FormatAPIFilterQuery, DecodeLocationSearch, + UpdateLocationSearch, NewUnappliedFilter }; diff --git a/ui/src/Stores/AlertStore.test.js b/ui/src/Stores/AlertStore.test.js index b8958bd1e..56cd6216a 100644 --- a/ui/src/Stores/AlertStore.test.js +++ b/ui/src/Stores/AlertStore.test.js @@ -5,6 +5,7 @@ import { AlertStoreStatuses, FormatUnseeBackendURI, DecodeLocationSearch, + UpdateLocationSearch, NewUnappliedFilter } from "Stores/AlertStore"; @@ -63,6 +64,13 @@ describe("AlertStore.filters", () => { expect(store.filters.values[0]).toMatchObject(NewUnappliedFilter("foo")); }); + it("addFilter should not allow duplicates", () => { + const store = new AlertStore([]); + store.filters.addFilter("foo"); + store.filters.addFilter("foo"); + expect(store.filters.values).toHaveLength(1); + }); + it("removeFilter('foo') should remove passed filter if it's defined", () => { const store = new AlertStore([]); store.filters.addFilter("foo"); @@ -180,6 +188,23 @@ describe("DecodeLocationSearch", () => { }); }); +describe("UpdateLocationSearch", () => { + it("{q: foo} is pushed to location.search", () => { + UpdateLocationSearch({ q: "foo" }); + expect(window.location.search).toBe("?q=foo"); + }); + + it("{a: foo} is not pushed to location.search", () => { + UpdateLocationSearch({ a: "foo" }); + expect(window.location.search).toBe(""); + }); + + it("{a: foo, q: bar} is pushed to location.search", () => { + UpdateLocationSearch({ a: "foo", q: "bar" }); + expect(window.location.search).toBe("?q=bar"); + }); +}); + describe("AlertStore.fetch", () => { it("parseAPIResponse() rejects a response with mismatched filters", () => { const consoleSpy = jest.spyOn(console, "info"); @@ -247,7 +272,34 @@ describe("AlertStore.fetch", () => { it("unapplied filters are marked as applied on fetch error", async () => { fetch.mockReject("Fetch error"); const store = new AlertStore([NewUnappliedFilter("foo")]); + store.filters.values[0].applied = false; await expect(store.fetch()).resolves.toHaveProperty("error"); expect(store.filters.values[0].applied).toBe(true); }); + + it("stored settings are updated if needed after fetch", async () => { + const response = EmptyAPIResponse(); + fetch.mockResponse(JSON.stringify(response)); + + const store = new AlertStore(["label=value"]); + + // initial fetch, should update settings + store.settings.values = { foo: "bar" }; + await expect(store.fetch()).resolves.toBeUndefined(); + expect(store.settings.values).toMatchObject({ + staticColorLabels: ["job"], + annotationsDefaultHidden: false, + annotationsHidden: [], + annotationsVisible: [] + }); + + // second fetch, should keep same settings + await expect(store.fetch()).resolves.toBeUndefined(); + expect(store.settings.values).toMatchObject({ + staticColorLabels: ["job"], + annotationsDefaultHidden: false, + annotationsHidden: [], + annotationsVisible: [] + }); + }); }); From e286cf2f1c4fef57d45b6166ba57d89b739c2500 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Sun, 2 Sep 2018 19:58:36 +0100 Subject: [PATCH 17/17] fix(ui): remove dead check --- .../Components/Grid/AlertGrid/AlertGroup/Annotation/index.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/Annotation/index.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/Annotation/index.js index 70eae7ec5..92819de8b 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/Annotation/index.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/Annotation/index.js @@ -29,8 +29,9 @@ const RenderNonLinkAnnotation = inject("alertStore")( { visible: true, show(e) { - // don't action link clicks inside Linkify - if (e.target.nodeName !== "A") this.visible = true; + // Linkify only handles value, no need to check for links of + // collapsed annotation + this.visible = true; }, hide(e) { // don't action link clicks inside Linkify