@@ -137,12 +147,15 @@ const TabContentDuration = observer(({ silenceFormStore }) => {
value={silenceFormStore.data.toDuration.minutes}
onInc={() =>
silenceFormStore.data.incEnd(
- CalculateChangeValue(silenceFormStore.data.toDuration.minutes, 5)
+ CalculateChangeValueUp(silenceFormStore.data.toDuration.minutes, 5)
)
}
onDec={() =>
silenceFormStore.data.decEnd(
- CalculateChangeValue(silenceFormStore.data.toDuration.minutes, 5)
+ CalculateChangeValueDown(
+ silenceFormStore.data.toDuration.minutes,
+ 5
+ )
)
}
/>
@@ -243,4 +256,4 @@ const DateTimeSelect = observer(
}
);
-export { DateTimeSelect };
+export { DateTimeSelect, TabContentStart, TabContentEnd, TabContentDuration };
diff --git a/ui/src/Components/SilenceModal/DateTimeSelect/index.test.js b/ui/src/Components/SilenceModal/DateTimeSelect/index.test.js
new file mode 100644
index 000000000..ec4a59d76
--- /dev/null
+++ b/ui/src/Components/SilenceModal/DateTimeSelect/index.test.js
@@ -0,0 +1,306 @@
+import React from "react";
+
+import { mount, shallow } from "enzyme";
+
+import moment from "moment";
+
+import { SilenceFormStore } from "Stores/SilenceFormStore";
+import {
+ DateTimeSelect,
+ TabContentStart,
+ TabContentEnd,
+ TabContentDuration
+} from ".";
+
+let silenceFormStore;
+
+beforeEach(() => {
+ silenceFormStore = new SilenceFormStore();
+ silenceFormStore.data.startsAt = moment([2060, 1, 1, 0, 0, 0]);
+ silenceFormStore.data.endsAt = moment([2061, 1, 1, 0, 0, 0]);
+});
+
+const ShallowDateTimeSelect = () => {
+ return shallow(
);
+};
+
+const MountedDateTimeSelect = () => {
+ return mount(
);
+};
+
+describe("
", () => {
+ it("renders 3 tabs", () => {
+ const tree = ShallowDateTimeSelect();
+ const tabs = tree.find("Tab");
+ expect(tabs).toHaveLength(3);
+ });
+
+ it("renders 'Duration' tab by default", () => {
+ const tree = MountedDateTimeSelect();
+ const tab = tree.find(".nav-link.active");
+ expect(tab).toHaveLength(1);
+ // check tab title
+ expect(tab.text()).toMatch(/Duration/);
+ // check tab content
+ expect(tree.find(".tab-content").text()).toBe("366days0hours0minutes");
+ });
+
+ it("clicking on the 'Starts' tab switches content to 'startsAt' selection", () => {
+ const tree = MountedDateTimeSelect();
+ const tab = tree.find(".nav-link").at(0);
+ expect(tab.text()).toMatch(/Starts/);
+ tab.simulate("click");
+ expect(tree.find(".tab-content").text()).toMatch(/2060/);
+ });
+
+ it("clicking on the 'Ends' tab switches content to 'endsAt' selection", () => {
+ const tree = MountedDateTimeSelect();
+ const tab = tree.find(".nav-link").at(1);
+ expect(tab.text()).toMatch(/Ends/);
+ tab.simulate("click");
+ expect(tree.find(".tab-content").text()).toMatch(/2061/);
+ });
+
+ it("clicking on the 'Duration' tabs switches content to duration selection", () => {
+ const tree = MountedDateTimeSelect();
+ // first switch to 'Starts'
+ tree
+ .find(".nav-link")
+ .at(0)
+ .simulate("click");
+ // then switch back to 'Duration'
+ const tab = tree.find(".nav-link").at(2);
+ expect(tab.text()).toMatch(/Duration/);
+ tab.simulate("click");
+ expect(tree.find(".tab-content").text()).toBe("366days0hours0minutes");
+ });
+});
+
+const ValidateTimeButton = (
+ tab,
+ storeKey,
+ elemIndex,
+ iconMatch,
+ expectedDiff
+) => {
+ const button = tab.find("td > span").at(elemIndex);
+ expect(button.html()).toMatch(iconMatch);
+
+ const oldTimeValue = moment(silenceFormStore.data[storeKey]);
+ button.simulate("click");
+ expect(silenceFormStore.data[storeKey].toISOString()).not.toBe(
+ oldTimeValue.toISOString()
+ );
+ const diffMS = silenceFormStore.data[storeKey].diff(oldTimeValue);
+ expect(diffMS).toBe(expectedDiff);
+};
+
+const ShallowTabContentStart = () => {
+ return shallow(
);
+};
+
+const MountedTabContentStart = () => {
+ return mount(
);
+};
+
+describe("
", () => {
+ it("selecting date on DatePicker updates startsAt", () => {
+ const tree = ShallowTabContentStart();
+ const picker = tree.find("DatePicker");
+ const startsAt = moment([2063, 10, 10, 0, 1, 2]);
+ picker.simulate("change", startsAt);
+ expect(silenceFormStore.data.startsAt.toISOString()).toBe(
+ startsAt.toISOString()
+ );
+ });
+
+ it("clicking on the hour inc button adds 1h to startsAt", () => {
+ const tree = MountedTabContentStart();
+ ValidateTimeButton(tree, "startsAt", 0, /angle-up/, 3600 * 1000);
+ });
+
+ it("clicking on the minute inc button adds 1m to startsAt", () => {
+ const tree = MountedTabContentStart();
+ ValidateTimeButton(tree, "startsAt", 1, /angle-up/, 60 * 1000);
+ });
+
+ it("clicking on the hour dec button subtracts 1h from startsAt", () => {
+ const tree = MountedTabContentStart();
+ ValidateTimeButton(tree, "startsAt", 2, /angle-down/, -1 * 3600 * 1000);
+ });
+
+ it("clicking on the minute dec button subtracts 1m from startsAt", () => {
+ const tree = MountedTabContentStart();
+ ValidateTimeButton(tree, "startsAt", 3, /angle-down/, -1 * 60 * 1000);
+ });
+});
+
+const ShallowTabContentEnd = () => {
+ return shallow(
);
+};
+
+const MountedTabContentEnd = () => {
+ return mount(
);
+};
+
+describe("
", () => {
+ it("Selecting date on DatePicker updates endsAt", () => {
+ const tree = ShallowTabContentEnd();
+ const picker = tree.find("DatePicker");
+ const endsAt = moment([2063, 11, 5, 1, 3, 2]);
+ picker.simulate("change", endsAt);
+ expect(silenceFormStore.data.endsAt.toISOString()).toBe(
+ endsAt.toISOString()
+ );
+ });
+
+ it("clicking on the hour inc button adds 1h to endsAt", () => {
+ const tree = MountedTabContentEnd();
+ ValidateTimeButton(tree, "endsAt", 0, /angle-up/, 3600 * 1000);
+ });
+
+ it("clicking on the minute inc button adds 1m to endsAt", () => {
+ const tree = MountedTabContentEnd();
+ ValidateTimeButton(tree, "endsAt", 1, /angle-up/, 60 * 1000);
+ });
+
+ it("clicking on the hour dec button subtracts 1h from endsAt", () => {
+ const tree = MountedTabContentEnd();
+ ValidateTimeButton(tree, "endsAt", 2, /angle-down/, -1 * 3600 * 1000);
+ });
+
+ it("clicking on the minute dec button subtracts 1m from endsAt", () => {
+ const tree = MountedTabContentEnd();
+ ValidateTimeButton(tree, "endsAt", 3, /angle-down/, -1 * 60 * 1000);
+ });
+});
+
+const ValidateDurationButton = (elemIndex, iconMatch, expectedDiff) => {
+ const tree = mount(
+
+ );
+ const button = tree.find("td > span").at(elemIndex);
+ expect(button.html()).toMatch(iconMatch);
+
+ const oldEndsAt = moment(silenceFormStore.data.endsAt);
+ button.simulate("click");
+ expect(silenceFormStore.data.endsAt.toISOString()).not.toBe(
+ oldEndsAt.toISOString()
+ );
+ const diffMS = silenceFormStore.data.endsAt.diff(oldEndsAt);
+ expect(diffMS).toBe(expectedDiff);
+};
+
+describe("
", () => {
+ it("clicking on the day inc button adds 1d to endsAt", () => {
+ ValidateDurationButton(0, /angle-up/, 24 * 3600 * 1000);
+ });
+
+ it("clicking on the day dec button subtracts 1d from endsAt", () => {
+ ValidateDurationButton(2, /angle-down/, -1 * 24 * 3600 * 1000);
+ });
+
+ it("clicking on the hour inc button adds 1h to endsAt", () => {
+ ValidateDurationButton(3, /angle-up/, 3600 * 1000);
+ });
+
+ it("clicking on the hour dec button subtracts 1h from endsAt", () => {
+ ValidateDurationButton(5, /angle-down/, -1 * 3600 * 1000);
+ });
+
+ it("clicking on the minute inc button adds 5m to endsAt", () => {
+ ValidateDurationButton(6, /angle-up/, 5 * 60 * 1000);
+ });
+
+ it("clicking on the minute dec button subtracts 5m from endsAt", () => {
+ ValidateDurationButton(8, /angle-down/, -1 * 5 * 60 * 1000);
+ });
+});
+
+const SetDurationTo = (hours, minutes) => {
+ const startsAt = moment([2060, 1, 1, 0, 0, 0]);
+ const endsAt = moment(startsAt)
+ .add(hours, "hours")
+ .add(minutes, "minutes");
+ silenceFormStore.data.startsAt = startsAt;
+ silenceFormStore.data.endsAt = endsAt;
+};
+
+describe("
inc minute CalculateChangeValue", () => {
+ it("inc on 0:1:0 duration sets 0:1:5", () => {
+ SetDurationTo(1, 0);
+ ValidateDurationButton(6, /angle-up/, 5 * 60 * 1000);
+ });
+
+ it("inc on 0:1:1 duration sets 0:1:2", () => {
+ SetDurationTo(1, 1);
+ ValidateDurationButton(6, /angle-up/, 60 * 1000);
+ });
+
+ it("inc on 0:1:4 duration sets 0:1:5", () => {
+ SetDurationTo(1, 4);
+ ValidateDurationButton(6, /angle-up/, 60 * 1000);
+ });
+
+ it("inc on 0:1:5 duration sets 0:1:10", () => {
+ SetDurationTo(1, 5);
+ ValidateDurationButton(6, /angle-up/, 5 * 60 * 1000);
+ });
+
+ it("inc on 0:1:6 duration sets 0:1:10", () => {
+ SetDurationTo(1, 6);
+ ValidateDurationButton(6, /angle-up/, 4 * 60 * 1000);
+ });
+
+ it("inc on 0:0:55 duration sets 0:1:0", () => {
+ SetDurationTo(0, 55);
+ ValidateDurationButton(6, /angle-up/, 5 * 60 * 1000);
+ });
+});
+
+describe("
dec minute CalculateChangeValue", () => {
+ it("inc on 0:1:0 duration sets 0:0:55", () => {
+ SetDurationTo(1, 0);
+ ValidateDurationButton(8, /angle-down/, -5 * 60 * 1000);
+ });
+
+ it("inc on 0:0:59 duration sets 0:0:55", () => {
+ SetDurationTo(0, 59);
+ ValidateDurationButton(8, /angle-down/, -4 * 60 * 1000);
+ });
+
+ it("inc on 0:0:56 duration sets 0:0:55", () => {
+ SetDurationTo(0, 56);
+ ValidateDurationButton(8, /angle-down/, -1 * 60 * 1000);
+ });
+
+ it("inc on 0:0:55 duration sets 0:0:50", () => {
+ SetDurationTo(1, 0);
+ ValidateDurationButton(8, /angle-down/, -5 * 60 * 1000);
+ });
+
+ it("inc on 0:1:10 duration sets 0:1:5", () => {
+ SetDurationTo(1, 10);
+ ValidateDurationButton(8, /angle-down/, -5 * 60 * 1000);
+ });
+
+ it("inc on 0:1:6 duration sets 0:1:5", () => {
+ SetDurationTo(1, 6);
+ ValidateDurationButton(8, /angle-down/, -1 * 60 * 1000);
+ });
+
+ it("inc on 0:1:5 duration sets 0:1:0", () => {
+ SetDurationTo(1, 5);
+ ValidateDurationButton(8, /angle-down/, -5 * 60 * 1000);
+ });
+
+ it("inc on 0:1:4 duration sets 0:1:3", () => {
+ SetDurationTo(1, 4);
+ ValidateDurationButton(8, /angle-down/, -1 * 60 * 1000);
+ });
+
+ it("inc on 0:1:1 duration sets 0:1:0", () => {
+ SetDurationTo(1, 1);
+ ValidateDurationButton(8, /angle-down/, -1 * 60 * 1000);
+ });
+});
diff --git a/ui/src/Components/SilenceModal/SilenceSubmitProgress.js b/ui/src/Components/SilenceModal/SilenceSubmitProgress.js
index 693291b3b..a19fd2fa3 100644
--- a/ui/src/Components/SilenceModal/SilenceSubmitProgress.js
+++ b/ui/src/Components/SilenceModal/SilenceSubmitProgress.js
@@ -51,6 +51,8 @@ const SilenceSubmitProgress = observer(
submitState = observable(
{
+ // store fetch result here, useful for testing
+ fetch: null,
value: SubmitState.InProgress,
result: null,
markDone(result) {
@@ -68,7 +70,7 @@ const SilenceSubmitProgress = observer(
handleAlertmanagerRequest = () => {
const { uri, payload } = this.props;
- fetch(`${uri}/api/v1/silences`, {
+ this.submitState.fetch = fetch(`${uri}/api/v1/silences`, {
method: "POST",
body: JSON.stringify(payload),
headers: {
@@ -91,8 +93,11 @@ const SilenceSubmitProgress = observer(
} else if (response.status === "error") {
this.submitState.markFailed(response.error);
} else {
- this.submitState.markFailed(JSON.strigify(response));
+ this.submitState.markFailed(JSON.stringify(response));
}
+
+ // return status so we can assert it in tests
+ return response.status;
};
componentDidMount() {
diff --git a/ui/src/Components/SilenceModal/SilenceSubmitProgress.test.js b/ui/src/Components/SilenceModal/SilenceSubmitProgress.test.js
new file mode 100644
index 000000000..eb64be239
--- /dev/null
+++ b/ui/src/Components/SilenceModal/SilenceSubmitProgress.test.js
@@ -0,0 +1,88 @@
+import React from "react";
+
+import { mount } from "enzyme";
+
+import { SilenceSubmitProgress } from "./SilenceSubmitProgress";
+
+const MountedSilenceSubmitProgress = () => {
+ return mount(
+
+ );
+};
+
+describe("
", () => {
+ it("sends a request on mount", () => {
+ MountedSilenceSubmitProgress();
+ expect(fetch.mock.calls).toHaveLength(1);
+ });
+
+ it("appends /api/v1/silences to the passed URI", () => {
+ MountedSilenceSubmitProgress();
+ const uri = fetch.mock.calls[0][0];
+ expect(uri).toBe("http://localhost/mock/api/v1/silences");
+ });
+
+ it("sends correct JSON payload", () => {
+ MountedSilenceSubmitProgress();
+ const payload = fetch.mock.calls[0][1];
+ expect(payload).toMatchObject({
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ foo: "bar" })
+ });
+ });
+
+ it("renders returned silence ID on successful fetch", async () => {
+ fetch.mockResponseOnce(
+ JSON.stringify({ status: "success", data: { silenceId: "123456789" } })
+ );
+ const tree = MountedSilenceSubmitProgress();
+ await expect(tree.instance().submitState.fetch).resolves.toBe("success");
+ // force re-render
+ tree.update();
+ const silenceLink = tree.find("a");
+ expect(silenceLink).toHaveLength(1);
+ expect(silenceLink.text()).toBe("123456789");
+ });
+
+ it("renders returned error message on failed fetch", async () => {
+ fetch.mockRejectOnce(new Error("mock error message"));
+ const tree = MountedSilenceSubmitProgress();
+ await expect(tree.instance().submitState.fetch).resolves.toBeUndefined();
+ expect(tree.text()).toBe("mockAlertmanagermock error message");
+ });
+
+ it("renders success icon on successful fetch", async () => {
+ fetch.mockResponseOnce(
+ JSON.stringify({ status: "success", data: { silenceId: "123" } })
+ );
+ const tree = MountedSilenceSubmitProgress();
+ await expect(tree.instance().submitState.fetch).resolves.toBe("success");
+ tree.update();
+ expect(tree.find("FontAwesomeIcon.text-success")).toHaveLength(1);
+ expect(tree.find("FontAwesomeIcon.text-danger")).toHaveLength(0);
+ });
+
+ it("renders error icon on failed fetch", async () => {
+ fetch.mockResponseOnce(JSON.stringify({ status: "error" }));
+ const tree = MountedSilenceSubmitProgress();
+ await expect(tree.instance().submitState.fetch).resolves.toBe("error");
+ tree.update();
+ expect(tree.find("FontAwesomeIcon.text-success")).toHaveLength(0);
+ expect(tree.find("FontAwesomeIcon.text-danger")).toHaveLength(1);
+ });
+
+ it("renders unhandled 'status' values in the response as error", async () => {
+ fetch.mockResponseOnce(JSON.stringify({ status: "unhandled" }));
+ const tree = MountedSilenceSubmitProgress();
+ await expect(tree.instance().submitState.fetch).resolves.toBe("unhandled");
+ tree.update();
+ expect(tree.find("FontAwesomeIcon.text-success")).toHaveLength(0);
+ expect(tree.find("FontAwesomeIcon.text-danger")).toHaveLength(1);
+ expect(tree.text()).toBe('mockAlertmanager{"status":"unhandled"}');
+ });
+});
diff --git a/ui/src/Components/SilenceModal/__snapshots__/AlertManagerInput.test.js.snap b/ui/src/Components/SilenceModal/__snapshots__/AlertManagerInput.test.js.snap
new file mode 100644
index 000000000..50da3b856
--- /dev/null
+++ b/ui/src/Components/SilenceModal/__snapshots__/AlertManagerInput.test.js.snap
@@ -0,0 +1,154 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`
matches snapshot 1`] = `
+
false,
+ "nodeType" => false,
+ "$$typeof" => false,
+ "@@__IMMUTABLE_LIST__@@" => false,
+ "@@__IMMUTABLE_SET__@@" => false,
+ "@@__IMMUTABLE_MAP__@@" => false,
+ "@@__IMMUTABLE_STACK__@@" => false,
+ "toJSON" => false,
+ },
+ "proxy": [Circular],
+ "target": Object {
+ "label": "am1",
+ "value": "http://am1.example.com",
+ Symbol(mobx administration): [Circular],
+ },
+ "values": Map {
+ "label" => "am1",
+ "value" => "http://am1.example.com",
+ },
+ },
+ },
+ Object {
+ "label": "am2",
+ "value": "http://am2.example.com",
+ Symbol(mobx administration): ObservableObjectAdministration$$1 {
+ "defaultEnhancer": [Function],
+ "keysAtom": Atom$$1 {
+ "diffValue": 0,
+ "isBeingObserved": false,
+ "isPendingUnobservation": false,
+ "lastAccessedBy": 0,
+ "lowestObserverState": 2,
+ "name": "Silence form store.alertmanagers[..].keys",
+ "observers": Set {},
+ },
+ "name": "Silence form store.alertmanagers[..]",
+ "pendingKeys": Map {
+ "cheerio" => false,
+ "nodeType" => false,
+ "$$typeof" => false,
+ "@@__IMMUTABLE_LIST__@@" => false,
+ "@@__IMMUTABLE_SET__@@" => false,
+ "@@__IMMUTABLE_MAP__@@" => false,
+ "@@__IMMUTABLE_STACK__@@" => false,
+ "toJSON" => false,
+ },
+ "proxy": [Circular],
+ "target": Object {
+ "label": "am2",
+ "value": "http://am2.example.com",
+ Symbol(mobx administration): [Circular],
+ },
+ "values": Map {
+ "label" => "am2",
+ "value" => "http://am2.example.com",
+ },
+ },
+ },
+ Object {
+ "label": "am3",
+ "value": "http://am3.example.com",
+ Symbol(mobx administration): ObservableObjectAdministration$$1 {
+ "defaultEnhancer": [Function],
+ "keysAtom": Atom$$1 {
+ "diffValue": 0,
+ "isBeingObserved": false,
+ "isPendingUnobservation": false,
+ "lastAccessedBy": 0,
+ "lowestObserverState": 2,
+ "name": "Silence form store.alertmanagers[..].keys",
+ "observers": Set {},
+ },
+ "name": "Silence form store.alertmanagers[..]",
+ "pendingKeys": Map {
+ "cheerio" => false,
+ "nodeType" => false,
+ "$$typeof" => false,
+ "@@__IMMUTABLE_LIST__@@" => false,
+ "@@__IMMUTABLE_SET__@@" => false,
+ "@@__IMMUTABLE_MAP__@@" => false,
+ "@@__IMMUTABLE_STACK__@@" => false,
+ "toJSON" => false,
+ },
+ "proxy": [Circular],
+ "target": Object {
+ "label": "am3",
+ "value": "http://am3.example.com",
+ Symbol(mobx administration): [Circular],
+ },
+ "values": Map {
+ "label" => "am3",
+ "value" => "http://am3.example.com",
+ },
+ },
+ },
+ ]
+ }
+ instanceId="silence-input-alertmanagers"
+ isMulti={true}
+ onChange={[Function]}
+ options={
+ Array [
+ Object {
+ "label": "am1",
+ "value": "http://am1.example.com",
+ },
+ Object {
+ "label": "am2",
+ "value": "http://am2.example.com",
+ },
+ Object {
+ "label": "am3",
+ "value": "http://am3.example.com",
+ },
+ ]
+ }
+ placeholder="Alertmanager"
+ styles={
+ Object {
+ "control": [Function],
+ "indicatorsContainer": [Function],
+ "multiValue": [Function],
+ "multiValueLabel": [Function],
+ "multiValueRemove": [Function],
+ "option": [Function],
+ "valueContainer": [Function],
+ }
+ }
+/>
+`;
diff --git a/ui/src/Components/SilenceModal/__snapshots__/LabelNameInput.test.js.snap b/ui/src/Components/SilenceModal/__snapshots__/LabelNameInput.test.js.snap
index 402cf5405..332b0ac3d 100644
--- a/ui/src/Components/SilenceModal/__snapshots__/LabelNameInput.test.js.snap
+++ b/ui/src/Components/SilenceModal/__snapshots__/LabelNameInput.test.js.snap
@@ -34,7 +34,6 @@ exports[` matches snapshot 1`] = `
"multiValueRemove": [Function],
"option": [Function],
"valueContainer": [Function],
- "valueLabel": [Function],
}
}
/>
diff --git a/ui/src/Components/SilenceModal/__snapshots__/LabelValueInput.test.js.snap b/ui/src/Components/SilenceModal/__snapshots__/LabelValueInput.test.js.snap
index bd95bf6d4..269f13542 100644
--- a/ui/src/Components/SilenceModal/__snapshots__/LabelValueInput.test.js.snap
+++ b/ui/src/Components/SilenceModal/__snapshots__/LabelValueInput.test.js.snap
@@ -30,7 +30,6 @@ exports[` matches snapshot 1`] = `
"multiValueRemove": [Function],
"option": [Function],
"valueContainer": [Function],
- "valueLabel": [Function],
}
}
/>
diff --git a/ui/src/Stores/AlertStore.js b/ui/src/Stores/AlertStore.js
index 6d0a4c6e0..21e03792d 100644
--- a/ui/src/Stores/AlertStore.js
+++ b/ui/src/Stores/AlertStore.js
@@ -238,6 +238,23 @@ class AlertStore {
return;
}
+ for (const filter of result.filters) {
+ const storedIndex = this.filters.values.findIndex(
+ f => f.raw === filter.text
+ );
+ this.filters.values[storedIndex] = Object.assign(
+ this.filters.values[storedIndex],
+ {
+ applied: true,
+ isValid: filter.isValid,
+ hits: filter.hits,
+ name: filter.name,
+ matcher: filter.matcher,
+ value: filter.value
+ }
+ );
+ }
+
let updates = {};
// update data dicts if they changed
for (const key of [
diff --git a/ui/src/Stores/AlertStore.test.js b/ui/src/Stores/AlertStore.test.js
index fb55d512e..b8958bd1e 100644
--- a/ui/src/Stores/AlertStore.test.js
+++ b/ui/src/Stores/AlertStore.test.js
@@ -204,6 +204,7 @@ describe("AlertStore.fetch", () => {
expect(store.status.value).toEqual(AlertStoreStatuses.Idle);
expect(store.info.version).toBe("fakeVersion");
+ expect(store.filters.values[0].applied).toBe(true);
});
it("fetch() works with valid response", async () => {
diff --git a/ui/src/setupTests.js b/ui/src/setupTests.js
index 776ea72d2..a18bc9688 100644
--- a/ui/src/setupTests.js
+++ b/ui/src/setupTests.js
@@ -12,6 +12,9 @@ mockConsole(["error", "warn", "info", "log", "trace"]);
// localStorage is used for Settings store
require("jest-localstorage-mock");
+// favico.js needs canvas
+require("jest-canvas-mock");
+
// fetch is used in multiple places to interact with Go backend
// or upstream Alertmanager API
global.fetch = require("jest-fetch-mock");