mirror of
https://github.com/prymitive/karma
synced 2026-09-04 11:27:19 +00:00
Merge pull request #76 from prymitive/tests-6
More test coverage for the UI code
This commit is contained in:
Generated
+6
@@ -6506,6 +6506,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"jest-canvas-mock": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jest-canvas-mock/-/jest-canvas-mock-1.1.0.tgz",
|
||||
"integrity": "sha512-D2VoKl+L6r9VpqTPygXKvIOQ1aou7gz3PvstlWDZqPT7EVYcSz0Nj+yjJ9G+Y9EqJd2X95f3dzcmmXb2dvQ1DQ==",
|
||||
"dev": true
|
||||
},
|
||||
"jest-changed-files": {
|
||||
"version": "20.0.3",
|
||||
"resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-20.0.3.tgz",
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
"enzyme-adapter-react-16": "1.3.0",
|
||||
"enzyme-to-json": "3.3.4",
|
||||
"eslint-plugin-react": "7.11.1",
|
||||
"jest-canvas-mock": "1.1.0",
|
||||
"jest-fetch-mock": "1.6.5",
|
||||
"jest-localstorage-mock": "2.2.0",
|
||||
"jest-mock-console": "0.4.0",
|
||||
|
||||
@@ -3,7 +3,7 @@ import PropTypes from "prop-types";
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
|
||||
import * as Favico from "favico.js";
|
||||
import Favico from "favico.js";
|
||||
|
||||
const FaviconBadge = observer(
|
||||
class FaviconBadge extends Component {
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import React from "react";
|
||||
|
||||
import { mount } from "enzyme";
|
||||
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { FaviconBadge } from ".";
|
||||
|
||||
let alertStore;
|
||||
|
||||
beforeEach(() => {
|
||||
alertStore = new AlertStore([]);
|
||||
});
|
||||
|
||||
const MountedFaviconBadge = () => {
|
||||
return mount(<FaviconBadge alertStore={alertStore} />);
|
||||
};
|
||||
|
||||
describe("<FaviconBadge />", () => {
|
||||
it("creates Favico instance on mount", () => {
|
||||
const tree = MountedFaviconBadge();
|
||||
const instance = tree.instance();
|
||||
expect(instance.favicon).toBeInstanceOf(Object);
|
||||
});
|
||||
|
||||
it("updateBadge is called when alertStore.info.totalAlerts changes", () => {
|
||||
const tree = MountedFaviconBadge();
|
||||
const instance = tree.instance();
|
||||
const updateSpy = jest.spyOn(instance, "updateBadge");
|
||||
alertStore.info.totalAlerts = 99;
|
||||
expect(updateSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("updateBadge is called when alertStore.status.error changes", () => {
|
||||
const tree = MountedFaviconBadge();
|
||||
const instance = tree.instance();
|
||||
const updateSpy = jest.spyOn(instance, "updateBadge");
|
||||
alertStore.status.error = "foo";
|
||||
expect(updateSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,105 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<CustomMultiSelect /> matches snapshot with a value 1`] = `
|
||||
<StateManager
|
||||
defaultInputValue=""
|
||||
defaultMenuIsOpen={false}
|
||||
defaultValue={
|
||||
Object {
|
||||
"label": "foo",
|
||||
"value": "foo",
|
||||
}
|
||||
}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
"label": "foo",
|
||||
"value": "foo",
|
||||
},
|
||||
]
|
||||
}
|
||||
styles={
|
||||
Object {
|
||||
"control": [Function],
|
||||
"indicatorsContainer": [Function],
|
||||
"multiValue": [Function],
|
||||
"multiValueLabel": [Function],
|
||||
"multiValueRemove": [Function],
|
||||
"option": [Function],
|
||||
"valueContainer": [Function],
|
||||
}
|
||||
}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`<CustomMultiSelect /> matches snapshot with defaults 1`] = `
|
||||
<StateManager
|
||||
defaultInputValue=""
|
||||
defaultMenuIsOpen={false}
|
||||
defaultValue={null}
|
||||
styles={
|
||||
Object {
|
||||
"control": [Function],
|
||||
"indicatorsContainer": [Function],
|
||||
"multiValue": [Function],
|
||||
"multiValueLabel": [Function],
|
||||
"multiValueRemove": [Function],
|
||||
"option": [Function],
|
||||
"valueContainer": [Function],
|
||||
}
|
||||
}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`<CustomMultiSelect /> matches snapshot with isMulti=true 1`] = `
|
||||
<StateManager
|
||||
defaultInputValue=""
|
||||
defaultMenuIsOpen={false}
|
||||
defaultValue={null}
|
||||
isMulti={true}
|
||||
styles={
|
||||
Object {
|
||||
"control": [Function],
|
||||
"indicatorsContainer": [Function],
|
||||
"multiValue": [Function],
|
||||
"multiValueLabel": [Function],
|
||||
"multiValueRemove": [Function],
|
||||
"option": [Function],
|
||||
"valueContainer": [Function],
|
||||
}
|
||||
}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`<CustomMultiSelect /> matches snapshot with isMulti=true and a value 1`] = `
|
||||
<StateManager
|
||||
defaultInputValue=""
|
||||
defaultMenuIsOpen={false}
|
||||
defaultValue={
|
||||
Object {
|
||||
"label": "foo",
|
||||
"value": "foo",
|
||||
}
|
||||
}
|
||||
isMulti={true}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
"label": "foo",
|
||||
"value": "foo",
|
||||
},
|
||||
]
|
||||
}
|
||||
styles={
|
||||
Object {
|
||||
"control": [Function],
|
||||
"indicatorsContainer": [Function],
|
||||
"multiValue": [Function],
|
||||
"multiValueLabel": [Function],
|
||||
"multiValueRemove": [Function],
|
||||
"option": [Function],
|
||||
"valueContainer": [Function],
|
||||
}
|
||||
}
|
||||
/>
|
||||
`;
|
||||
@@ -41,11 +41,6 @@ const ReactSelectStyles = {
|
||||
borderRadius: "0.25rem",
|
||||
backgroundColor: "#fff"
|
||||
},
|
||||
valueLabel: (base, state) => ({
|
||||
...base,
|
||||
whiteSpace: "normal",
|
||||
wordWrap: "break-word"
|
||||
}),
|
||||
multiValue: (base, state) => ({
|
||||
...base,
|
||||
borderRadius: "4px",
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import React from "react";
|
||||
|
||||
import { shallow } from "enzyme";
|
||||
|
||||
import { MultiSelect } from ".";
|
||||
|
||||
const Option = value => ({ label: value, value: value });
|
||||
|
||||
class CustomMultiSelect extends MultiSelect {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.extraProps = props;
|
||||
}
|
||||
|
||||
renderProps = () => this.extraProps;
|
||||
}
|
||||
|
||||
describe("<CustomMultiSelect />", () => {
|
||||
it("matches snapshot with defaults", () => {
|
||||
const tree = shallow(<CustomMultiSelect />);
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("matches snapshot with isMulti=true", () => {
|
||||
const tree = shallow(<CustomMultiSelect isMulti />);
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("matches snapshot with a value", () => {
|
||||
const tree = shallow(
|
||||
<CustomMultiSelect
|
||||
defaultValue={Option("foo")}
|
||||
options={[Option("foo", Option("bar"))]}
|
||||
/>
|
||||
);
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("matches snapshot with isMulti=true and a value", () => {
|
||||
const tree = shallow(
|
||||
<CustomMultiSelect
|
||||
isMulti
|
||||
defaultValue={Option("foo")}
|
||||
options={[Option("foo", Option("bar"))]}
|
||||
/>
|
||||
);
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -19,3 +19,9 @@ input.components-filterinput-wrapper {
|
||||
input.components-filterinput-wrapper:focus {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
/* highlighted part of the suggestion - phrase in the input that matches it */
|
||||
mark.highlight {
|
||||
padding: 0;
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ const FilterInput = observer(
|
||||
{
|
||||
ref: null,
|
||||
suggestions: [],
|
||||
suggestionsFetch: null,
|
||||
value: "",
|
||||
storeInputReference(ref) {
|
||||
this.ref = ref;
|
||||
@@ -49,11 +50,10 @@ const FilterInput = observer(
|
||||
}
|
||||
|
||||
onChange = action((event, { newValue, method }) => {
|
||||
if (method === "enter") {
|
||||
event.preventDefault();
|
||||
} else {
|
||||
this.inputStore.value = newValue;
|
||||
}
|
||||
// onChange here handles change for the user input in the filter bar
|
||||
// we need to update inputStore.value every time user types in something
|
||||
event.preventDefault();
|
||||
this.inputStore.value = newValue;
|
||||
});
|
||||
|
||||
onSubmit = action(event => {
|
||||
@@ -71,7 +71,9 @@ const FilterInput = observer(
|
||||
onSuggestionsFetchRequested = debounce(
|
||||
action(({ value }) => {
|
||||
if (value !== "") {
|
||||
fetch(FormatUnseeBackendURI(`autocomplete.json?term=${value}`))
|
||||
this.inputStore.suggestionsFetch = fetch(
|
||||
FormatUnseeBackendURI(`autocomplete.json?term=${value}`)
|
||||
)
|
||||
.then(
|
||||
result => result.json(),
|
||||
err => {
|
||||
|
||||
@@ -22,6 +22,12 @@ const MountedInput = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const WaitForFetch = tree => {
|
||||
return expect(
|
||||
tree.instance().inputStore.suggestionsFetch
|
||||
).resolves.toBeUndefined();
|
||||
};
|
||||
|
||||
describe("<FilterInput />", () => {
|
||||
it("matches snapshot on default render", () => {
|
||||
const tree = render(
|
||||
@@ -68,38 +74,66 @@ describe("<FilterInput />", () => {
|
||||
});
|
||||
|
||||
describe("<FilterInput Autosuggest />", () => {
|
||||
it("fetches suggestions on input change", done => {
|
||||
it("fetches suggestions on input change", async () => {
|
||||
fetch.mockResponseOnce(JSON.stringify(["foo=bar", "foo=~bar"]));
|
||||
|
||||
const tree = MountedInput();
|
||||
const instance = tree.instance();
|
||||
tree.find("input").simulate("change", { target: { value: "foo" } });
|
||||
await WaitForFetch(tree);
|
||||
|
||||
// need to wait on fetch to resolve, but can't find any better way here
|
||||
setTimeout(() => {
|
||||
expect(fetch.mock.calls).toHaveLength(1);
|
||||
expect(fetch.mock.calls[0]).toContain("./autocomplete.json?term=foo");
|
||||
expect(instance.inputStore.suggestions).toHaveLength(2);
|
||||
expect(instance.inputStore.suggestions).toContain("foo=bar");
|
||||
expect(instance.inputStore.suggestions).toContain("foo=~bar");
|
||||
done();
|
||||
}, 1000);
|
||||
expect(fetch.mock.calls).toHaveLength(1);
|
||||
expect(fetch.mock.calls[0]).toContain("./autocomplete.json?term=foo");
|
||||
expect(instance.inputStore.suggestions).toHaveLength(2);
|
||||
expect(instance.inputStore.suggestions).toContain("foo=bar");
|
||||
expect(instance.inputStore.suggestions).toContain("foo=~bar");
|
||||
});
|
||||
|
||||
it("handles failed suggestion fetches", done => {
|
||||
it("clicking on a suggestion adds it to filters", async () => {
|
||||
fetch.mockResponse(JSON.stringify(["foo=bar", "foo=~bar"]));
|
||||
|
||||
const tree = MountedInput();
|
||||
tree.find("input").simulate("change", { target: { value: "foo" } });
|
||||
// suggestions are rendered only when input is focused
|
||||
tree.find("input").simulate("focus");
|
||||
await WaitForFetch(tree);
|
||||
|
||||
// find() doesn't pick up suggestions even when tree.html() shows them
|
||||
// forcing update seems to solve it
|
||||
// https://github.com/airbnb/enzyme/issues/1233#issuecomment-343449560
|
||||
tree.update();
|
||||
// not sure why but suggestions are being found twice
|
||||
const suggestion = tree.find(".dropdown-item").at(2);
|
||||
expect(suggestion.text()).toBe("foo=~bar");
|
||||
suggestion.simulate("click");
|
||||
expect(alertStore.filters.values).toHaveLength(1);
|
||||
expect(alertStore.filters.values[0]).toMatchObject({ raw: "foo=~bar" });
|
||||
});
|
||||
|
||||
it("handles failed suggestion fetches", async () => {
|
||||
fetch.mockRejectOnce("Fetch error");
|
||||
|
||||
const tree = MountedInput();
|
||||
const instance = tree.instance();
|
||||
tree.find("input").simulate("change", { target: { value: "bar" } });
|
||||
await WaitForFetch(tree);
|
||||
|
||||
// need to wait on fetch to resolve, but can't find any better way here
|
||||
setTimeout(() => {
|
||||
expect(fetch.mock.calls).toHaveLength(1);
|
||||
expect(fetch.mock.calls[0]).toContain("./autocomplete.json?term=bar");
|
||||
expect(instance.inputStore.suggestions).toHaveLength(0);
|
||||
done();
|
||||
}, 1000);
|
||||
expect(fetch.mock.calls).toHaveLength(1);
|
||||
expect(fetch.mock.calls[0]).toContain("./autocomplete.json?term=bar");
|
||||
expect(instance.inputStore.suggestions).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("handles invalid JSON in suggestion fetches", async () => {
|
||||
fetch.mockResponseOnce("this is not JSON");
|
||||
|
||||
const tree = MountedInput();
|
||||
const instance = tree.instance();
|
||||
tree.find("input").simulate("change", { target: { value: "bar" } });
|
||||
await WaitForFetch(tree);
|
||||
|
||||
expect(fetch.mock.calls).toHaveLength(1);
|
||||
expect(fetch.mock.calls[0]).toContain("./autocomplete.json?term=bar");
|
||||
expect(instance.inputStore.suggestions).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("clearing input clears suggestions", () => {
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
import React from "react";
|
||||
|
||||
import { shallow, mount } from "enzyme";
|
||||
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { SilenceFormStore } from "Stores/SilenceFormStore";
|
||||
import { AlertManagerInput } from "./AlertManagerInput";
|
||||
|
||||
let alertStore;
|
||||
let silenceFormStore;
|
||||
|
||||
const AlertmanagerOption = index => ({
|
||||
label: `am${index}`,
|
||||
value: `http://am${index}.example.com`
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
alertStore = new AlertStore([]);
|
||||
alertStore.data.upstreams.instances = [
|
||||
{ name: "am1", uri: "http://am1.example.com", error: "" },
|
||||
{ name: "am2", uri: "http://am2.example.com", error: "" },
|
||||
{ name: "am3", uri: "http://am3.example.com", error: "" }
|
||||
];
|
||||
silenceFormStore = new SilenceFormStore();
|
||||
});
|
||||
|
||||
const ShallowAlertManagerInput = () => {
|
||||
return shallow(
|
||||
<AlertManagerInput
|
||||
alertStore={alertStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const MountedAlertManagerInput = () => {
|
||||
return mount(
|
||||
<AlertManagerInput
|
||||
alertStore={alertStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const ValidateSuggestions = () => {
|
||||
const tree = MountedAlertManagerInput();
|
||||
// clear all selected instances, they are selected by default
|
||||
const clear = tree.find("ClearIndicator");
|
||||
// https://github.com/JedWatson/react-select/blob/c22d296d50917e210836fb011ae3e565895e6440/src/__tests__/Select.test.js#L1873
|
||||
clear.simulate("mousedown", { button: 0 });
|
||||
// click on the react-select component doesn't seem to trigger options
|
||||
// rendering in tests, so change the input instead
|
||||
tree.find("input").simulate("change", { target: { value: "am" } });
|
||||
return tree;
|
||||
};
|
||||
|
||||
describe("<AlertManagerInput />", () => {
|
||||
it("matches snapshot", () => {
|
||||
const tree = ShallowAlertManagerInput();
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("all available Alertmanager instances are selected by default", () => {
|
||||
ShallowAlertManagerInput();
|
||||
expect(silenceFormStore.data.alertmanagers).toHaveLength(3);
|
||||
for (let i = 1; i <= 3; i++) {
|
||||
expect(silenceFormStore.data.alertmanagers).toContainEqual(
|
||||
AlertmanagerOption(i)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("renders all 3 suggestions", () => {
|
||||
const tree = ValidateSuggestions();
|
||||
const options = tree.find("[role='option']");
|
||||
expect(options).toHaveLength(3);
|
||||
expect(options.at(0).text()).toBe("am1");
|
||||
expect(options.at(1).text()).toBe("am2");
|
||||
expect(options.at(2).text()).toBe("am3");
|
||||
});
|
||||
|
||||
it("clicking on options appends them to silenceFormStore.data.alertmanagers", () => {
|
||||
const tree = ValidateSuggestions();
|
||||
const options = tree.find("[role='option']");
|
||||
options.at(0).simulate("click");
|
||||
options.at(2).simulate("click");
|
||||
expect(silenceFormStore.data.alertmanagers).toHaveLength(2);
|
||||
expect(silenceFormStore.data.alertmanagers).toContainEqual(
|
||||
AlertmanagerOption(1)
|
||||
);
|
||||
expect(silenceFormStore.data.alertmanagers).toContainEqual(
|
||||
AlertmanagerOption(3)
|
||||
);
|
||||
});
|
||||
|
||||
it("silenceFormStore.data.alertmanagers gets updated from alertStore.data.upstreams.instances on mismatch", () => {
|
||||
const tree = ShallowAlertManagerInput();
|
||||
alertStore.data.upstreams.instances[0] = {
|
||||
name: "am1",
|
||||
uri: "http://am1.example.com/new",
|
||||
error: ""
|
||||
};
|
||||
// force update since this is where the mismatch check lives
|
||||
tree.instance().componentDidUpdate();
|
||||
expect(silenceFormStore.data.alertmanagers).toContainEqual({
|
||||
label: "am1",
|
||||
value: "http://am1.example.com/new"
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -11,7 +11,7 @@ const Duration = observer(
|
||||
class Duration extends Component {
|
||||
static propTypes = {
|
||||
value: PropTypes.number.isRequired,
|
||||
label: PropTypes.string,
|
||||
label: PropTypes.string.isRequired,
|
||||
onInc: PropTypes.func.isRequired,
|
||||
onDec: PropTypes.func.isRequired
|
||||
};
|
||||
@@ -40,9 +40,7 @@ const Duration = observer(
|
||||
<h2>{value}</h2>
|
||||
</td>
|
||||
<td className="w-50">
|
||||
{label ? (
|
||||
<span className="text-muted ml-2">{label}</span>
|
||||
) : null}
|
||||
<span className="text-muted ml-2">{label}</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@@ -107,8 +107,8 @@ const TabContentEnd = observer(({ silenceFormStore }) => {
|
||||
);
|
||||
});
|
||||
|
||||
// calculate value for duration increase and decrease buttons using a goal step
|
||||
const CalculateChangeValue = (currentValue, step) => {
|
||||
// calculate value for duration increase button using a goal step
|
||||
const CalculateChangeValueUp = (currentValue, step) => {
|
||||
// if current value is less than step (but >0) then use 1
|
||||
if (currentValue > 0 && currentValue < step) {
|
||||
return 1;
|
||||
@@ -117,6 +117,16 @@ const CalculateChangeValue = (currentValue, step) => {
|
||||
return step - (currentValue % step) || step;
|
||||
};
|
||||
|
||||
// calculate value for duration decrease button using a goal step
|
||||
const CalculateChangeValueDown = (currentValue, step) => {
|
||||
// if current value is less than step (but >0) then use 1
|
||||
if (currentValue > 0 && currentValue < step) {
|
||||
return 1;
|
||||
}
|
||||
// otherwise use step or a value that moves current value to the next step
|
||||
return currentValue % step || step;
|
||||
};
|
||||
|
||||
const TabContentDuration = observer(({ silenceFormStore }) => {
|
||||
return (
|
||||
<div className="d-flex flex-sm-row flex-column justify-content-around mt-2 mx-3">
|
||||
@@ -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 };
|
||||
|
||||
@@ -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(<DateTimeSelect silenceFormStore={silenceFormStore} />);
|
||||
};
|
||||
|
||||
const MountedDateTimeSelect = () => {
|
||||
return mount(<DateTimeSelect silenceFormStore={silenceFormStore} />);
|
||||
};
|
||||
|
||||
describe("<DateTimeSelect />", () => {
|
||||
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(<TabContentStart silenceFormStore={silenceFormStore} />);
|
||||
};
|
||||
|
||||
const MountedTabContentStart = () => {
|
||||
return mount(<TabContentStart silenceFormStore={silenceFormStore} />);
|
||||
};
|
||||
|
||||
describe("<TabContentStart />", () => {
|
||||
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(<TabContentEnd silenceFormStore={silenceFormStore} />);
|
||||
};
|
||||
|
||||
const MountedTabContentEnd = () => {
|
||||
return mount(<TabContentEnd silenceFormStore={silenceFormStore} />);
|
||||
};
|
||||
|
||||
describe("<TabContentEnd />", () => {
|
||||
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(
|
||||
<TabContentDuration silenceFormStore={silenceFormStore} />
|
||||
);
|
||||
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("<TabContentDuration />", () => {
|
||||
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("<TabContentDuration /> 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("<TabContentDuration /> 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);
|
||||
});
|
||||
});
|
||||
@@ -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() {
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
import React from "react";
|
||||
|
||||
import { mount } from "enzyme";
|
||||
|
||||
import { SilenceSubmitProgress } from "./SilenceSubmitProgress";
|
||||
|
||||
const MountedSilenceSubmitProgress = () => {
|
||||
return mount(
|
||||
<SilenceSubmitProgress
|
||||
name="mockAlertmanager"
|
||||
uri="http://localhost/mock"
|
||||
payload={{ foo: "bar" }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
describe("<SilenceSubmitProgress />", () => {
|
||||
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"}');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,154 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<AlertManagerInput /> matches snapshot 1`] = `
|
||||
<StateManager
|
||||
defaultInputValue=""
|
||||
defaultMenuIsOpen={false}
|
||||
defaultValue={
|
||||
Array [
|
||||
Object {
|
||||
"label": "am1",
|
||||
"value": "http://am1.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": "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],
|
||||
}
|
||||
}
|
||||
/>
|
||||
`;
|
||||
@@ -34,7 +34,6 @@ exports[`<LabelNameInput /> matches snapshot 1`] = `
|
||||
"multiValueRemove": [Function],
|
||||
"option": [Function],
|
||||
"valueContainer": [Function],
|
||||
"valueLabel": [Function],
|
||||
}
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -30,7 +30,6 @@ exports[`<LabelValueInput /> matches snapshot 1`] = `
|
||||
"multiValueRemove": [Function],
|
||||
"option": [Function],
|
||||
"valueContainer": [Function],
|
||||
"valueLabel": [Function],
|
||||
}
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -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 [
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user