mirror of
https://github.com/prymitive/karma
synced 2026-05-07 03:26:52 +00:00
fix(ui): rewrite Browser component with hooks
This commit is contained in:
committed by
Łukasz Mierzwa
parent
f9c007d3bd
commit
2dbbbb542c
@@ -1,8 +1,7 @@
|
||||
import React, { Component, useContext } from "react";
|
||||
import React, { useEffect, useCallback } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import { observable, action } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import { useObserver, useLocalStore } from "mobx-react";
|
||||
|
||||
import debounce from "lodash/debounce";
|
||||
|
||||
@@ -35,7 +34,7 @@ FetchError.propTypes = {
|
||||
};
|
||||
|
||||
const Placeholder = ({ content }) => {
|
||||
const theme = useContext(ThemeContext);
|
||||
const theme = React.useContext(ThemeContext);
|
||||
|
||||
return (
|
||||
<Fade in={theme.animations.in} duration={theme.animations.duration}>
|
||||
@@ -49,230 +48,185 @@ Placeholder.propTypes = {
|
||||
content: PropTypes.node.isRequired,
|
||||
};
|
||||
|
||||
const Browser = observer(
|
||||
class Browser extends Component {
|
||||
static propTypes = {
|
||||
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
|
||||
silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired,
|
||||
settingsStore: PropTypes.instanceOf(Settings).isRequired,
|
||||
onDeleteModalClose: PropTypes.func.isRequired,
|
||||
};
|
||||
const Browser = ({
|
||||
alertStore,
|
||||
silenceFormStore,
|
||||
settingsStore,
|
||||
onDeleteModalClose,
|
||||
}) => {
|
||||
const dataSource = useLocalStore(() => ({
|
||||
silences: [],
|
||||
sortReverse: false,
|
||||
showExpired: false,
|
||||
searchTerm: "",
|
||||
error: null,
|
||||
fetch: null,
|
||||
done: false,
|
||||
setDone() {
|
||||
this.done = true;
|
||||
},
|
||||
setError(value) {
|
||||
this.error = value;
|
||||
},
|
||||
toggleSortReverse() {
|
||||
this.sortReverse = !this.sortReverse;
|
||||
},
|
||||
toggleShowExpired() {
|
||||
this.showExpired = !this.showExpired;
|
||||
},
|
||||
setSearchTerm(value) {
|
||||
this.searchTerm = value;
|
||||
},
|
||||
}));
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.fetchTimer = null;
|
||||
}
|
||||
const maxPerPage = 5;
|
||||
|
||||
dataSource = observable(
|
||||
{
|
||||
silences: [],
|
||||
sortReverse: false,
|
||||
showExpired: false,
|
||||
searchTerm: "",
|
||||
error: null,
|
||||
fetch: null,
|
||||
done: false,
|
||||
setDone() {
|
||||
this.done = true;
|
||||
},
|
||||
setError(value) {
|
||||
this.error = value;
|
||||
},
|
||||
toggleSortReverse() {
|
||||
this.sortReverse = !this.sortReverse;
|
||||
},
|
||||
toggleShowExpired() {
|
||||
this.showExpired = !this.showExpired;
|
||||
},
|
||||
setSearchTerm(value) {
|
||||
this.searchTerm = value;
|
||||
},
|
||||
},
|
||||
{
|
||||
setDone: action.bound,
|
||||
setError: action.bound,
|
||||
toggleSortReverse: action.bound,
|
||||
toggleShowExpired: action.bound,
|
||||
setSearchTerm: action.bound,
|
||||
const pagination = useLocalStore(() => ({
|
||||
activePage: 1,
|
||||
onPageChange(pageNumber) {
|
||||
this.activePage = pageNumber;
|
||||
},
|
||||
resetIfNeeded(totalItemsCount, maxPerPage) {
|
||||
const totalPages = Math.ceil(totalItemsCount / maxPerPage);
|
||||
if (this.activePage > totalPages) {
|
||||
this.activePage = Math.max(1, totalPages);
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
const onFetch = useCallback(() => {
|
||||
const uri = FormatBackendURI(
|
||||
`silences.json?sortReverse=${
|
||||
dataSource.sortReverse ? "1" : "0"
|
||||
}&showExpired=${dataSource.showExpired ? "1" : "0"}&searchTerm=${
|
||||
dataSource.searchTerm
|
||||
}`
|
||||
);
|
||||
|
||||
onFetch = () => {
|
||||
const uri = FormatBackendURI(
|
||||
`silences.json?sortReverse=${
|
||||
this.dataSource.sortReverse ? "1" : "0"
|
||||
}&showExpired=${this.dataSource.showExpired ? "1" : "0"}&searchTerm=${
|
||||
this.dataSource.searchTerm
|
||||
}`
|
||||
);
|
||||
dataSource.fetch = FetchGet(uri, {})
|
||||
.then((result) => {
|
||||
return result.json();
|
||||
})
|
||||
.then((result) => {
|
||||
dataSource.silences = result;
|
||||
dataSource.setDone();
|
||||
dataSource.setError(null);
|
||||
pagination.resetIfNeeded(dataSource.silences.length, maxPerPage);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.trace(err);
|
||||
dataSource.setDone();
|
||||
return dataSource.setError(`Request failed with: ${err.message}`);
|
||||
});
|
||||
}, [dataSource, pagination]);
|
||||
|
||||
this.dataSource.fetch = FetchGet(uri, {})
|
||||
.then((result) => {
|
||||
return result.json();
|
||||
})
|
||||
.then((result) => {
|
||||
this.dataSource.silences = result;
|
||||
this.dataSource.setDone();
|
||||
this.dataSource.setError(null);
|
||||
this.pagination.resetIfNeeded(
|
||||
this.dataSource.silences.length,
|
||||
this.maxPerPage
|
||||
);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.trace(err);
|
||||
this.dataSource.setDone();
|
||||
return this.dataSource.setError(
|
||||
`Request failed with: ${err.message}`
|
||||
);
|
||||
});
|
||||
};
|
||||
const onDebouncedFetch = debounce(onFetch, 500);
|
||||
|
||||
onDebouncedFetch = debounce(this.onFetch, 500);
|
||||
useEffect(() => {
|
||||
onFetch();
|
||||
const timer = setInterval(() => {
|
||||
onFetch();
|
||||
}, settingsStore.fetchConfig.config.interval * 1000);
|
||||
return () => clearInterval(timer);
|
||||
}, [onFetch, settingsStore.fetchConfig.config.interval]);
|
||||
|
||||
maxPerPage = 5;
|
||||
|
||||
pagination = observable(
|
||||
{
|
||||
activePage: 1,
|
||||
onPageChange(pageNumber) {
|
||||
this.activePage = pageNumber;
|
||||
},
|
||||
resetIfNeeded(totalItemsCount, maxPerPage) {
|
||||
const totalPages = Math.ceil(totalItemsCount / maxPerPage);
|
||||
if (this.activePage > totalPages) {
|
||||
this.activePage = Math.max(1, totalPages);
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
onPageChange: action.bound,
|
||||
resetIfNeeded: action.bound,
|
||||
}
|
||||
);
|
||||
|
||||
componentDidMount() {
|
||||
const { settingsStore } = this.props;
|
||||
|
||||
this.onFetch();
|
||||
this.fetchTimer = setInterval(
|
||||
this.onFetch,
|
||||
settingsStore.fetchConfig.config.interval * 1000
|
||||
);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
clearInterval(this.fetchTimer);
|
||||
this.fetchTimer = null;
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
alertStore,
|
||||
silenceFormStore,
|
||||
settingsStore,
|
||||
onDeleteModalClose,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div
|
||||
className="d-flex flex-fill flex-lg-row flex-column justify-content-between mb-3"
|
||||
data-refresh={settingsStore.fetchConfig.config.interval}
|
||||
>
|
||||
<span className="custom-control custom-switch my-auto flex-grow-0 flex-shrink-0">
|
||||
<input
|
||||
id="silence-show-expired"
|
||||
className="custom-control-input"
|
||||
type="checkbox"
|
||||
value=""
|
||||
checked={this.dataSource.showExpired}
|
||||
onChange={() => {
|
||||
this.dataSource.toggleShowExpired();
|
||||
this.onDebouncedFetch();
|
||||
}}
|
||||
/>
|
||||
<label
|
||||
className="custom-control-label cursor-pointer"
|
||||
htmlFor="silence-show-expired"
|
||||
>
|
||||
Show expired
|
||||
</label>
|
||||
</span>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control flex-grow-1 flex-shrink-1 mx-lg-3 mx-0 my-lg-0 my-2"
|
||||
placeholder="Search query"
|
||||
value={this.dataSource.searchTerm}
|
||||
autoComplete="off"
|
||||
onChange={(e) => {
|
||||
this.dataSource.setSearchTerm(e.target.value);
|
||||
this.onDebouncedFetch();
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary flex-grow-0 flex-shrink-0"
|
||||
onClick={() => {
|
||||
this.dataSource.toggleSortReverse();
|
||||
this.onDebouncedFetch();
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
className="mr-1"
|
||||
icon={
|
||||
this.dataSource.sortReverse
|
||||
? faSortAmountUp
|
||||
: faSortAmountDownAlt
|
||||
}
|
||||
/>
|
||||
Sort order
|
||||
</button>
|
||||
</div>
|
||||
{this.dataSource.error !== null ? (
|
||||
<FetchError message={this.dataSource.error} />
|
||||
) : this.dataSource.done ? (
|
||||
this.dataSource.silences.length === 0 ? (
|
||||
<Placeholder content="Nothing to show" />
|
||||
) : (
|
||||
<React.Fragment>
|
||||
{this.dataSource.silences
|
||||
.slice(
|
||||
(this.pagination.activePage - 1) * this.maxPerPage,
|
||||
this.pagination.activePage * this.maxPerPage
|
||||
)
|
||||
.map((silence) => (
|
||||
<ManagedSilence
|
||||
key={`${silence.cluster}/${silence.silence.id}`}
|
||||
cluster={silence.cluster}
|
||||
alertCount={silence.alertCount}
|
||||
alertCountAlwaysVisible={true}
|
||||
silence={silence.silence}
|
||||
alertStore={alertStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
onDeleteModalClose={onDeleteModalClose}
|
||||
/>
|
||||
))}
|
||||
</React.Fragment>
|
||||
)
|
||||
) : (
|
||||
<Placeholder
|
||||
content={<FontAwesomeIcon icon={faSpinner} size="lg" spin />}
|
||||
/>
|
||||
)}
|
||||
<PageSelect
|
||||
totalPages={Math.ceil(
|
||||
this.dataSource.silences.length / this.maxPerPage
|
||||
)}
|
||||
activePage={this.pagination.activePage}
|
||||
maxPerPage={this.maxPerPage}
|
||||
totalItemsCount={this.dataSource.silences.length}
|
||||
setPageCallback={this.pagination.onPageChange}
|
||||
return useObserver(() => (
|
||||
<React.Fragment>
|
||||
<div
|
||||
className="d-flex flex-fill flex-lg-row flex-column justify-content-between mb-3"
|
||||
data-refresh={settingsStore.fetchConfig.config.interval}
|
||||
>
|
||||
<span className="custom-control custom-switch my-auto flex-grow-0 flex-shrink-0">
|
||||
<input
|
||||
id="silence-show-expired"
|
||||
className="custom-control-input"
|
||||
type="checkbox"
|
||||
value=""
|
||||
checked={dataSource.showExpired}
|
||||
onChange={() => {
|
||||
dataSource.toggleShowExpired();
|
||||
onDebouncedFetch();
|
||||
}}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
<label
|
||||
className="custom-control-label cursor-pointer"
|
||||
htmlFor="silence-show-expired"
|
||||
>
|
||||
Show expired
|
||||
</label>
|
||||
</span>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control flex-grow-1 flex-shrink-1 mx-lg-3 mx-0 my-lg-0 my-2"
|
||||
placeholder="Search query"
|
||||
value={dataSource.searchTerm}
|
||||
autoComplete="off"
|
||||
onChange={(e) => {
|
||||
dataSource.setSearchTerm(e.target.value);
|
||||
onDebouncedFetch();
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary flex-grow-0 flex-shrink-0"
|
||||
onClick={() => {
|
||||
dataSource.toggleSortReverse();
|
||||
onDebouncedFetch();
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
className="mr-1"
|
||||
icon={dataSource.sortReverse ? faSortAmountUp : faSortAmountDownAlt}
|
||||
/>
|
||||
Sort order
|
||||
</button>
|
||||
</div>
|
||||
{dataSource.error !== null ? (
|
||||
<FetchError message={dataSource.error} />
|
||||
) : dataSource.done ? (
|
||||
dataSource.silences.length === 0 ? (
|
||||
<Placeholder content="Nothing to show" />
|
||||
) : (
|
||||
<React.Fragment>
|
||||
{dataSource.silences
|
||||
.slice(
|
||||
(pagination.activePage - 1) * maxPerPage,
|
||||
pagination.activePage * maxPerPage
|
||||
)
|
||||
.map((silence) => (
|
||||
<ManagedSilence
|
||||
key={`${silence.cluster}/${silence.silence.id}`}
|
||||
cluster={silence.cluster}
|
||||
alertCount={silence.alertCount}
|
||||
alertCountAlwaysVisible={true}
|
||||
silence={silence.silence}
|
||||
alertStore={alertStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
onDeleteModalClose={onDeleteModalClose}
|
||||
/>
|
||||
))}
|
||||
</React.Fragment>
|
||||
)
|
||||
) : (
|
||||
<Placeholder
|
||||
content={<FontAwesomeIcon icon={faSpinner} size="lg" spin />}
|
||||
/>
|
||||
)}
|
||||
<PageSelect
|
||||
totalPages={Math.ceil(dataSource.silences.length / maxPerPage)}
|
||||
activePage={pagination.activePage}
|
||||
maxPerPage={maxPerPage}
|
||||
totalItemsCount={dataSource.silences.length}
|
||||
setPageCallback={pagination.onPageChange}
|
||||
/>
|
||||
</React.Fragment>
|
||||
));
|
||||
};
|
||||
Browser.propTypes = {
|
||||
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
|
||||
silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired,
|
||||
settingsStore: PropTypes.instanceOf(Settings).isRequired,
|
||||
onDeleteModalClose: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export { Browser };
|
||||
|
||||
@@ -31,6 +31,8 @@ beforeEach(() => {
|
||||
cluster = "am";
|
||||
silence = MockSilence();
|
||||
|
||||
settingsStore.fetchConfig.config.interval = 30;
|
||||
|
||||
alertStore.data.upstreams = {
|
||||
instances: [
|
||||
{
|
||||
@@ -48,15 +50,14 @@ beforeEach(() => {
|
||||
],
|
||||
clusters: { am: ["am1"] },
|
||||
};
|
||||
|
||||
fetch.resetMocks();
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
fetch.resetMocks();
|
||||
clear();
|
||||
|
||||
localStorage.setItem("fetchConfig.interval", "");
|
||||
});
|
||||
|
||||
const MockOnDeleteModalClose = jest.fn();
|
||||
@@ -91,7 +92,7 @@ const MountedBrowser = () => {
|
||||
};
|
||||
|
||||
describe("<Browser />", () => {
|
||||
it("fetches /silences.json on mount", async () => {
|
||||
it("fetches /silences.json on mount", (done) => {
|
||||
fetch.mockResponse(
|
||||
JSON.stringify([
|
||||
{
|
||||
@@ -101,14 +102,26 @@ describe("<Browser />", () => {
|
||||
},
|
||||
])
|
||||
);
|
||||
const tree = MountedBrowser();
|
||||
await expect(tree.instance().dataSource.fetch).resolves.toBeUndefined();
|
||||
expect(fetch.mock.calls[0][0]).toBe(
|
||||
"./silences.json?sortReverse=0&showExpired=0&searchTerm="
|
||||
);
|
||||
MountedBrowser();
|
||||
setTimeout(() => {
|
||||
expect(fetch.mock.calls[0][0]).toBe(
|
||||
"./silences.json?sortReverse=0&showExpired=0&searchTerm="
|
||||
);
|
||||
done();
|
||||
}, 200);
|
||||
});
|
||||
|
||||
it("enabling reverse sort passes sortReverse=1 to the API", async () => {
|
||||
it("fetches /silences.json in a loop", (done) => {
|
||||
settingsStore.fetchConfig.config.interval = 1;
|
||||
fetch.mockResponse(JSON.stringify([]));
|
||||
MountedBrowser();
|
||||
setTimeout(() => {
|
||||
expect(fetch.mock.calls).toHaveLength(4);
|
||||
done();
|
||||
}, 1100 * 3);
|
||||
});
|
||||
|
||||
it("enabling reverse sort passes sortReverse=1 to the API", (done) => {
|
||||
fetch.mockResponse(
|
||||
JSON.stringify([
|
||||
{
|
||||
@@ -124,13 +137,15 @@ describe("<Browser />", () => {
|
||||
expect(sortOrder.text()).toBe("Sort order");
|
||||
sortOrder.simulate("click");
|
||||
|
||||
await expect(tree.instance().dataSource.fetch).resolves.toBeUndefined();
|
||||
expect(fetch.mock.calls[1][0]).toBe(
|
||||
"./silences.json?sortReverse=1&showExpired=0&searchTerm="
|
||||
);
|
||||
setTimeout(() => {
|
||||
expect(fetch.mock.calls[1][0]).toBe(
|
||||
"./silences.json?sortReverse=1&showExpired=0&searchTerm="
|
||||
);
|
||||
done();
|
||||
}, 200);
|
||||
});
|
||||
|
||||
it("enabling expired silences passes showExpired=1 to the API", async () => {
|
||||
it("enabling expired silences passes showExpired=1 to the API", (done) => {
|
||||
fetch.mockResponse(
|
||||
JSON.stringify([
|
||||
{
|
||||
@@ -145,42 +160,50 @@ describe("<Browser />", () => {
|
||||
const expiredCheckbox = tree.find("input[type='checkbox']");
|
||||
expiredCheckbox.simulate("change", { target: { checked: true } });
|
||||
|
||||
await expect(tree.instance().dataSource.fetch).resolves.toBeUndefined();
|
||||
expect(fetch.mock.calls[1][0]).toBe(
|
||||
"./silences.json?sortReverse=0&showExpired=1&searchTerm="
|
||||
);
|
||||
setTimeout(() => {
|
||||
expect(fetch.mock.calls[1][0]).toBe(
|
||||
"./silences.json?sortReverse=0&showExpired=1&searchTerm="
|
||||
);
|
||||
done();
|
||||
}, 200);
|
||||
});
|
||||
|
||||
it("entering a search phrase passes searchTerm=foo to the API", async () => {
|
||||
it("entering a search phrase passes searchTerm=foo to the API", (done) => {
|
||||
fetch.mockResponse(JSON.stringify([]));
|
||||
const tree = MountedBrowser();
|
||||
|
||||
const input = tree.find("input[type='text']").at(0);
|
||||
input.simulate("change", { target: { value: "foo" } });
|
||||
|
||||
await expect(tree.instance().dataSource.fetch).resolves.toBeUndefined();
|
||||
expect(fetch.mock.calls[1][0]).toBe(
|
||||
"./silences.json?sortReverse=0&showExpired=0&searchTerm=foo"
|
||||
);
|
||||
setTimeout(() => {
|
||||
expect(fetch.mock.calls[1][0]).toBe(
|
||||
"./silences.json?sortReverse=0&showExpired=0&searchTerm=foo"
|
||||
);
|
||||
done();
|
||||
}, 200);
|
||||
});
|
||||
|
||||
it("renders loading placeholder before fetch finishes", async () => {
|
||||
it("renders loading placeholder before fetch finishes", (done) => {
|
||||
fetch.mockResponse(JSON.stringify([]));
|
||||
const tree = MountedBrowser();
|
||||
expect(tree.find("Placeholder")).toHaveLength(1);
|
||||
expect(toDiffableHtml(tree.html())).toMatch(/fa-spinner/);
|
||||
await expect(tree.instance().dataSource.fetch).resolves.toBeUndefined();
|
||||
setTimeout(() => {
|
||||
done();
|
||||
}, 200);
|
||||
});
|
||||
|
||||
it("renders empty placeholder after fetch with zero results", async () => {
|
||||
it("renders empty placeholder after fetch with zero results", (done) => {
|
||||
fetch.mockResponse(JSON.stringify([]));
|
||||
const tree = MountedBrowser();
|
||||
await expect(tree.instance().dataSource.fetch).resolves.toBeUndefined();
|
||||
expect(tree.find("Placeholder")).toHaveLength(1);
|
||||
expect(toDiffableHtml(tree.html())).toMatch(/Nothing to show/);
|
||||
setTimeout(() => {
|
||||
expect(tree.find("Placeholder")).toHaveLength(1);
|
||||
expect(toDiffableHtml(tree.html())).toMatch(/Nothing to show/);
|
||||
done();
|
||||
}, 200);
|
||||
});
|
||||
|
||||
it("renders silences after successful fetch", async () => {
|
||||
it("renders silences after successful fetch", (done) => {
|
||||
fetch.mockResponse(
|
||||
JSON.stringify([
|
||||
{
|
||||
@@ -191,118 +214,138 @@ describe("<Browser />", () => {
|
||||
])
|
||||
);
|
||||
const tree = MountedBrowser();
|
||||
await expect(tree.instance().dataSource.fetch).resolves.toBeUndefined();
|
||||
tree.update();
|
||||
expect(tree.find("ManagedSilence")).toHaveLength(1);
|
||||
setTimeout(() => {
|
||||
tree.update();
|
||||
expect(tree.find("ManagedSilence")).toHaveLength(1);
|
||||
done();
|
||||
}, 200);
|
||||
});
|
||||
|
||||
it("renders only first 5 silences", async () => {
|
||||
it("renders only first 5 silences", (done) => {
|
||||
fetch.mockResponse(JSON.stringify(MockSilenceList(6)));
|
||||
const tree = MountedBrowser();
|
||||
await expect(tree.instance().dataSource.fetch).resolves.toBeUndefined();
|
||||
tree.update();
|
||||
expect(tree.find("ManagedSilence")).toHaveLength(5);
|
||||
setTimeout(() => {
|
||||
tree.update();
|
||||
expect(tree.find("ManagedSilence")).toHaveLength(5);
|
||||
done();
|
||||
}, 200);
|
||||
});
|
||||
|
||||
it("renders last silence after page change", async () => {
|
||||
it("renders last silence after page change", (done) => {
|
||||
fetch.mockResponse(JSON.stringify(MockSilenceList(6)));
|
||||
const tree = MountedBrowser();
|
||||
await expect(tree.instance().dataSource.fetch).resolves.toBeUndefined();
|
||||
tree.update();
|
||||
expect(tree.instance().pagination.activePage).toBe(1);
|
||||
expect(tree.find("ManagedSilence")).toHaveLength(5);
|
||||
|
||||
const pageLink = tree.find(".page-link").at(3);
|
||||
pageLink.simulate("click");
|
||||
tree.update();
|
||||
expect(tree.instance().pagination.activePage).toBe(2);
|
||||
expect(tree.find("ManagedSilence")).toHaveLength(1);
|
||||
setTimeout(() => {
|
||||
tree.update();
|
||||
expect(tree.find("li.page-item").at(1).hasClass("active")).toBe(true);
|
||||
expect(tree.find("ManagedSilence")).toHaveLength(5);
|
||||
|
||||
const pageLink = tree.find(".page-link").at(3);
|
||||
pageLink.simulate("click");
|
||||
tree.update();
|
||||
expect(tree.find("li.page-item").at(2).hasClass("active")).toBe(true);
|
||||
expect(tree.find("ManagedSilence")).toHaveLength(1);
|
||||
done();
|
||||
}, 200);
|
||||
});
|
||||
|
||||
it("renders next/previous page after arrow key press", async () => {
|
||||
it("renders next/previous page after arrow key press", (done) => {
|
||||
fetch.mockResponse(JSON.stringify(MockSilenceList(11)));
|
||||
const tree = MountedBrowser();
|
||||
await expect(tree.instance().dataSource.fetch).resolves.toBeUndefined();
|
||||
tree.update();
|
||||
expect(tree.instance().pagination.activePage).toBe(1);
|
||||
expect(tree.find("ManagedSilence")).toHaveLength(5);
|
||||
|
||||
const paginator = tree.find(".components-pagination").at(0);
|
||||
paginator.simulate("focus");
|
||||
setTimeout(() => {
|
||||
tree.update();
|
||||
expect(tree.find("li.page-item").at(1).hasClass("active")).toBe(true);
|
||||
expect(tree.find("ManagedSilence")).toHaveLength(5);
|
||||
|
||||
PressKey(paginator, "ArrowRight", 39);
|
||||
expect(tree.instance().pagination.activePage).toBe(2);
|
||||
expect(tree.find("ManagedSilence")).toHaveLength(5);
|
||||
const paginator = tree.find(".components-pagination").at(0);
|
||||
paginator.simulate("focus");
|
||||
|
||||
PressKey(paginator, "ArrowRight", 39);
|
||||
expect(tree.instance().pagination.activePage).toBe(3);
|
||||
expect(tree.find("ManagedSilence")).toHaveLength(1);
|
||||
PressKey(paginator, "ArrowRight", 39);
|
||||
expect(tree.find("li.page-item").at(2).hasClass("active")).toBe(true);
|
||||
expect(tree.find("ManagedSilence")).toHaveLength(5);
|
||||
|
||||
PressKey(paginator, "ArrowRight", 39);
|
||||
expect(tree.instance().pagination.activePage).toBe(3);
|
||||
expect(tree.find("ManagedSilence")).toHaveLength(1);
|
||||
PressKey(paginator, "ArrowRight", 39);
|
||||
expect(tree.find("li.page-item").at(3).hasClass("active")).toBe(true);
|
||||
expect(tree.find("ManagedSilence")).toHaveLength(1);
|
||||
|
||||
PressKey(paginator, "ArrowLeft", 37);
|
||||
expect(tree.instance().pagination.activePage).toBe(2);
|
||||
expect(tree.find("ManagedSilence")).toHaveLength(5);
|
||||
PressKey(paginator, "ArrowRight", 39);
|
||||
expect(tree.find("li.page-item").at(3).hasClass("active")).toBe(true);
|
||||
expect(tree.find("ManagedSilence")).toHaveLength(1);
|
||||
|
||||
PressKey(paginator, "ArrowLeft", 37);
|
||||
expect(tree.instance().pagination.activePage).toBe(1);
|
||||
expect(tree.find("ManagedSilence")).toHaveLength(5);
|
||||
PressKey(paginator, "ArrowLeft", 37);
|
||||
expect(tree.find("li.page-item").at(2).hasClass("active")).toBe(true);
|
||||
expect(tree.find("ManagedSilence")).toHaveLength(5);
|
||||
|
||||
PressKey(paginator, "ArrowLeft", 37);
|
||||
expect(tree.instance().pagination.activePage).toBe(1);
|
||||
expect(tree.find("ManagedSilence")).toHaveLength(5);
|
||||
PressKey(paginator, "ArrowLeft", 37);
|
||||
expect(tree.find("li.page-item").at(1).hasClass("active")).toBe(true);
|
||||
expect(tree.find("ManagedSilence")).toHaveLength(5);
|
||||
|
||||
PressKey(paginator, "ArrowLeft", 37);
|
||||
expect(tree.find("li.page-item").at(1).hasClass("active")).toBe(true);
|
||||
expect(tree.find("ManagedSilence")).toHaveLength(5);
|
||||
done();
|
||||
}, 200);
|
||||
});
|
||||
|
||||
it("resets pagination to last page on truncation", async () => {
|
||||
fetch.mockResponseOnce(JSON.stringify(MockSilenceList(11)));
|
||||
it("resets pagination to last page on truncation", (done) => {
|
||||
fetch.mockResponse(JSON.stringify(MockSilenceList(11)));
|
||||
const tree = MountedBrowser();
|
||||
const instance = tree.instance();
|
||||
await expect(instance.dataSource.fetch).resolves.toBeUndefined();
|
||||
tree.update();
|
||||
setTimeout(() => {
|
||||
tree.update();
|
||||
expect(tree.find("li.page-item").at(1).hasClass("active")).toBe(true);
|
||||
const pageLink = tree.find(".page-link").at(3);
|
||||
pageLink.simulate("click");
|
||||
tree.update();
|
||||
expect(tree.find("ManagedSilence")).toHaveLength(1);
|
||||
expect(tree.find("li.page-item").at(3).hasClass("active")).toBe(true);
|
||||
|
||||
expect(instance.pagination.activePage).toBe(1);
|
||||
const pageLink = tree.find(".page-link").at(3);
|
||||
pageLink.simulate("click");
|
||||
tree.update();
|
||||
expect(tree.find("ManagedSilence")).toHaveLength(1);
|
||||
expect(instance.pagination.activePage).toBe(3);
|
||||
fetch.mockResponse(JSON.stringify(MockSilenceList(7)));
|
||||
tree.find("button.btn-secondary").simulate("click");
|
||||
|
||||
fetch.mockResponseOnce(JSON.stringify(MockSilenceList(7)));
|
||||
instance.onFetch();
|
||||
await expect(instance.dataSource.fetch).resolves.toBeUndefined();
|
||||
tree.update();
|
||||
setTimeout(() => {
|
||||
tree.update();
|
||||
|
||||
expect(tree.find("ManagedSilence")).toHaveLength(2);
|
||||
expect(instance.pagination.activePage).toBe(2);
|
||||
expect(tree.find("ManagedSilence")).toHaveLength(2);
|
||||
expect(tree.find("li.page-item").at(2).hasClass("active")).toBe(true);
|
||||
|
||||
fetch.mockResponseOnce(JSON.stringify([]));
|
||||
instance.onFetch();
|
||||
await expect(instance.dataSource.fetch).resolves.toBeUndefined();
|
||||
tree.update();
|
||||
fetch.mockResponse(JSON.stringify([]));
|
||||
tree.find("button.btn-secondary").simulate("click");
|
||||
|
||||
expect(tree.find("ManagedSilence")).toHaveLength(0);
|
||||
expect(instance.pagination.activePage).toBe(1);
|
||||
setTimeout(() => {
|
||||
tree.update();
|
||||
|
||||
expect(tree.find("ManagedSilence")).toHaveLength(0);
|
||||
expect(tree.find("Placeholder")).toHaveLength(1);
|
||||
done();
|
||||
}, 200);
|
||||
}, 200);
|
||||
}, 200);
|
||||
});
|
||||
|
||||
it("renders error after failed fetch", async () => {
|
||||
it("renders error after failed fetch", (done) => {
|
||||
jest.spyOn(console, "trace").mockImplementation(() => {});
|
||||
fetch.mockReject("fake failure");
|
||||
const tree = MountedBrowser();
|
||||
await expect(tree.instance().dataSource.fetch).resolves.toBeUndefined();
|
||||
tree.update();
|
||||
expect(tree.find("FetchError")).toHaveLength(1);
|
||||
expect(toDiffableHtml(tree.html())).toMatch(/exclamation-circle/);
|
||||
setTimeout(() => {
|
||||
tree.update();
|
||||
expect(tree.find("FetchError")).toHaveLength(1);
|
||||
expect(toDiffableHtml(tree.html())).toMatch(/exclamation-circle/);
|
||||
done();
|
||||
}, 200);
|
||||
});
|
||||
|
||||
it("resets the timer on unmount", async () => {
|
||||
it("resets the timer on unmount", (done) => {
|
||||
fetch.mockResponse(JSON.stringify([]));
|
||||
const tree = MountedBrowser();
|
||||
await expect(tree.instance().dataSource.fetch).resolves.toBeUndefined();
|
||||
setTimeout(() => {
|
||||
expect(fetch.mock.calls).toHaveLength(1);
|
||||
|
||||
expect(tree.instance().fetchTimer).not.toBeNull();
|
||||
tree.instance().componentWillUnmount();
|
||||
expect(tree.instance().fetchTimer).toBeNull();
|
||||
setTimeout(() => {
|
||||
tree.unmount();
|
||||
expect(fetch.mock.calls).toHaveLength(1);
|
||||
done();
|
||||
});
|
||||
}, 200);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user