diff --git a/ui/src/App.test.js b/ui/src/App.test.js
index 9862a8e57..36f95d8d4 100644
--- a/ui/src/App.test.js
+++ b/ui/src/App.test.js
@@ -21,6 +21,7 @@ beforeEach(() => {
});
afterEach(() => {
+ localStorage.setItem("savedFilters", "");
jest.restoreAllMocks();
window.history.pushState({}, "App", "/");
});
@@ -119,4 +120,38 @@ describe("", () => {
NewUnappliedFilter("use=query")
);
});
+
+ it("popstate event updates alertStore filters", () => {
+ const tree = shallow(
+
+ );
+ expect(tree.instance().alertStore.filters.values).toHaveLength(1);
+ expect(tree.instance().alertStore.filters.values[0]).toMatchObject(
+ NewUnappliedFilter("foo")
+ );
+
+ delete global.window.location;
+ global.window.location = {
+ href: "http://localhost/?q=bar",
+ search: "?q=bar"
+ };
+
+ let event = new PopStateEvent("popstate");
+ window.onpopstate(event);
+
+ expect(tree.instance().alertStore.filters.values).toHaveLength(1);
+ expect(tree.instance().alertStore.filters.values[0]).toMatchObject(
+ NewUnappliedFilter("bar")
+ );
+ });
+
+ it("unmounts without crashing", () => {
+ const tree = shallow(
+
+ );
+ tree.instance().componentWillUnmount();
+
+ let event = new PopStateEvent("popstate");
+ window.onpopstate(event);
+ });
});
diff --git a/ui/src/App.tsx b/ui/src/App.tsx
index 3a1906491..00f962665 100644
--- a/ui/src/App.tsx
+++ b/ui/src/App.tsx
@@ -62,6 +62,20 @@ class App extends Component {
this.alertStore = new AlertStore(filters);
}
+ onPopState = (event: PopStateEvent) => {
+ event.preventDefault();
+ const p = DecodeLocationSearch(window.location.search);
+ this.alertStore.filters.setWithoutLocation(p.params.q);
+ };
+
+ componentDidMount() {
+ window.onpopstate = this.onPopState;
+ }
+
+ componentWillUnmount() {
+ window.onpopstate = () => {};
+ }
+
render() {
return (
diff --git a/ui/src/Stores/AlertStore.js b/ui/src/Stores/AlertStore.js
index d19c1e1ad..8e187606d 100644
--- a/ui/src/Stores/AlertStore.js
+++ b/ui/src/Stores/AlertStore.js
@@ -1,4 +1,4 @@
-import { observable, action } from "mobx";
+import { observable, action, toJS } from "mobx";
import { throttle } from "lodash";
@@ -128,6 +128,15 @@ class AlertStore {
setFilters(raws) {
this.values = raws.map(raw => NewUnappliedFilter(raw));
UpdateLocationSearch({ q: this.values.map(f => f.raw) });
+ },
+ setWithoutLocation(raws) {
+ const filtersByRaw = this.values.reduce(function(map, obj) {
+ map[toJS(obj.raw)] = toJS(obj);
+ return map;
+ }, {});
+ this.values = raws.map(raw =>
+ filtersByRaw[raw] ? filtersByRaw[raw] : NewUnappliedFilter(raw)
+ );
}
},
{
diff --git a/ui/src/Stores/AlertStore.test.js b/ui/src/Stores/AlertStore.test.js
index 210ccba93..02c29099d 100644
--- a/ui/src/Stores/AlertStore.test.js
+++ b/ui/src/Stores/AlertStore.test.js
@@ -132,6 +132,81 @@ describe("AlertStore.filters", () => {
expect(store.filters.values).toHaveLength(1);
expect(store.filters.values[0]).toMatchObject(NewUnappliedFilter("bar"));
});
+
+ it("addFilter() updates window.history", () => {
+ const store = new AlertStore([]);
+ const historyMock = jest.spyOn(global.window.history, "pushState");
+ store.filters.addFilter("foo");
+ expect(historyMock).toHaveBeenLastCalledWith(
+ null,
+ null,
+ "http://localhost/?q=foo"
+ );
+ });
+
+ it("replaceFilter() updates window.history", () => {
+ const store = new AlertStore(["foo"]);
+ const historyMock = jest.spyOn(global.window.history, "pushState");
+ store.filters.replaceFilter("foo", "bar");
+ expect(historyMock).toHaveBeenLastCalledWith(
+ null,
+ null,
+ "http://localhost/?q=bar"
+ );
+ });
+
+ it("addFilter() updates window.history", () => {
+ const store = new AlertStore([]);
+ const historyMock = jest.spyOn(global.window.history, "pushState");
+ store.filters.addFilter("foo");
+ expect(historyMock).toHaveBeenLastCalledWith(
+ null,
+ null,
+ "http://localhost/?q=foo"
+ );
+ });
+
+ it("setFilters() updates window.history", () => {
+ const store = new AlertStore([]);
+ store.filters.addFilter("foo");
+ store.filters.addFilter("bar");
+
+ const historyMock = jest.spyOn(global.window.history, "pushState");
+ store.filters.setFilters(["baz", "far"]);
+ expect(store.filters.values).toHaveLength(2);
+ expect(store.filters.values[0]).toMatchObject(NewUnappliedFilter("baz"));
+ expect(store.filters.values[1]).toMatchObject(NewUnappliedFilter("far"));
+ expect(historyMock).toHaveBeenLastCalledWith(
+ null,
+ null,
+ "http://localhost/?q=baz&q=far"
+ );
+ });
+
+ it("setWithoutLocation() doesn't update window.history", () => {
+ const store = new AlertStore(["far", "foo"]);
+
+ const historyMock = jest.spyOn(global.window.history, "pushState");
+ store.filters.setWithoutLocation(["baz", "far"]);
+ expect(store.filters.values).toHaveLength(2);
+ expect(store.filters.values[0]).toMatchObject(NewUnappliedFilter("baz"));
+ expect(store.filters.values[1]).toMatchObject(NewUnappliedFilter("far"));
+ expect(historyMock).not.toHaveBeenCalled();
+ });
+
+ it("setWithoutLocation() adds missing filters", () => {
+ const store = new AlertStore([]);
+ store.filters.setWithoutLocation(["foo", "bar"]);
+ expect(store.filters.values).toHaveLength(2);
+ expect(store.filters.values[0]).toMatchObject(NewUnappliedFilter("foo"));
+ expect(store.filters.values[1]).toMatchObject(NewUnappliedFilter("bar"));
+ });
+
+ it("setWithoutLocation() removes orphaned filters", () => {
+ const store = new AlertStore(["far"]);
+ store.filters.setWithoutLocation([]);
+ expect(store.filters.values).toHaveLength(0);
+ });
});
describe("FormatBackendURI", () => {