mirror of
https://github.com/prymitive/karma
synced 2026-05-09 03:36:44 +00:00
Merge pull request #85 from prymitive/missing-branches
Missing branches
This commit is contained in:
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -59,6 +59,18 @@ const MountedNonLinkAnnotation = visible => {
|
||||
);
|
||||
};
|
||||
|
||||
const MountedNonLinkAnnotationContainingLink = visible => {
|
||||
return mount(
|
||||
<RenderNonLinkAnnotation
|
||||
alertStore={alertStore}
|
||||
name="foo"
|
||||
value="some long text with http://example.com link"
|
||||
visible={visible}
|
||||
afterUpdate={MockAfterUpdate}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
describe("<RenderNonLinkAnnotation />", () => {
|
||||
it("matches snapshot when visible=true", () => {
|
||||
const tree = ShallowNonLinkAnnotation(true);
|
||||
@@ -80,6 +92,12 @@ describe("<RenderNonLinkAnnotation />", () => {
|
||||
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("<RenderNonLinkAnnotation />", () => {
|
||||
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/);
|
||||
|
||||
@@ -112,6 +112,9 @@ exports[`<Silence /> matches snapshot with expaned details 1`] = `
|
||||
<span class=\\"badge badge-success text-nowrap text-truncate px-1 mr-1\\">
|
||||
alertname=MockAlert
|
||||
</span>
|
||||
<span class=\\"badge badge-success text-nowrap text-truncate px-1 mr-1\\">
|
||||
instance=~foo[0-9]+
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -65,6 +65,27 @@ describe("<AlertGroup />", () => {
|
||||
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);
|
||||
|
||||
@@ -89,6 +89,15 @@ describe("<AlertGrid />", () => {
|
||||
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();
|
||||
|
||||
34
ui/src/Components/Labels/HistoryLabel/index.test.js
Normal file
34
ui/src/Components/Labels/HistoryLabel/index.test.js
Normal file
@@ -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("<HistoryLabel />", () => {
|
||||
it("renders name, matcher and value if all are set", () => {
|
||||
const tree = shallow(
|
||||
<HistoryLabel
|
||||
alertStore={alertStore}
|
||||
name="foo"
|
||||
matcher="="
|
||||
value="bar"
|
||||
/>
|
||||
);
|
||||
expect(tree.text()).toBe("foo=bar");
|
||||
});
|
||||
|
||||
it("renders only value if name is falsey", () => {
|
||||
const tree = shallow(
|
||||
<HistoryLabel alertStore={alertStore} name="" matcher="" value="bar" />
|
||||
);
|
||||
expect(tree.text()).toBe("bar");
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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" }
|
||||
})
|
||||
|
||||
@@ -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,29 +27,29 @@ class CustomMultiSelect extends MultiSelect {
|
||||
describe("<CustomMultiSelect />", () => {
|
||||
it("matches snapshot with defaults", () => {
|
||||
const tree = shallow(<CustomMultiSelect />);
|
||||
expect(tree).toMatchSnapshot();
|
||||
expect(toDiffableHtml(tree.html())).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("matches snapshot with isMulti=true", () => {
|
||||
const tree = shallow(<CustomMultiSelect isMulti />);
|
||||
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(<CustomMultiSelect autoFocus />);
|
||||
tree.find("input").simulate("focus");
|
||||
expect(tree).toMatchSnapshot();
|
||||
expect(toDiffableHtml(tree.html())).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("matches snapshot with a value", () => {
|
||||
const tree = shallow(
|
||||
<CustomMultiSelect
|
||||
defaultValue={Option("foo")}
|
||||
options={[Option("foo", Option("bar"))]}
|
||||
options={[Option("foo"), Option("bar")]}
|
||||
/>
|
||||
);
|
||||
expect(tree).toMatchSnapshot();
|
||||
expect(toDiffableHtml(tree.html())).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("matches snapshot with isMulti=true and a value", () => {
|
||||
@@ -55,9 +57,9 @@ describe("<CustomMultiSelect />", () => {
|
||||
<CustomMultiSelect
|
||||
isMulti
|
||||
defaultValue={Option("foo")}
|
||||
options={[Option("foo", Option("bar"))]}
|
||||
options={[Option("foo"), Option("bar")]}
|
||||
/>
|
||||
);
|
||||
expect(tree).toMatchSnapshot();
|
||||
expect(toDiffableHtml(tree.html())).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -63,7 +63,16 @@ describe("<FilterInput />", () => {
|
||||
);
|
||||
});
|
||||
|
||||
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("<FilterInput />", () => {
|
||||
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("<FilterInput Autosuggest />", () => {
|
||||
@@ -89,6 +114,15 @@ describe("<FilterInput Autosuggest />", () => {
|
||||
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"]));
|
||||
|
||||
|
||||
@@ -70,6 +70,15 @@ describe("<AlertManagerInput />", () => {
|
||||
}
|
||||
});
|
||||
|
||||
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']");
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -77,7 +77,7 @@ describe("<LabelNameInput />", () => {
|
||||
}, 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("<LabelNameInput />", () => {
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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: []
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user