Merge pull request #1096 from prymitive/back-button

fix(ui): correctly handle browser back/forward buttons
This commit is contained in:
Łukasz Mierzwa
2019-10-28 14:11:26 +00:00
committed by GitHub
4 changed files with 134 additions and 1 deletions

View File

@@ -21,6 +21,7 @@ beforeEach(() => {
});
afterEach(() => {
localStorage.setItem("savedFilters", "");
jest.restoreAllMocks();
window.history.pushState({}, "App", "/");
});
@@ -119,4 +120,38 @@ describe("<App />", () => {
NewUnappliedFilter("use=query")
);
});
it("popstate event updates alertStore filters", () => {
const tree = shallow(
<App defaultFilters={["foo"]} uiDefaults={uiDefaults} />
);
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(
<App defaultFilters={["foo=bar"]} uiDefaults={uiDefaults} />
);
tree.instance().componentWillUnmount();
let event = new PopStateEvent("popstate");
window.onpopstate(event);
});
});

View File

@@ -62,6 +62,20 @@ class App extends Component<AppProps, {}> {
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 (
<ErrorBoundary>

View File

@@ -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)
);
}
},
{

View File

@@ -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", () => {