From 86815c95ddac2dc00a87ab445ca2ef1bac2e24c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Tue, 21 Aug 2018 20:22:08 +0100 Subject: [PATCH 1/4] feat(test): more test coverage for labels --- .../FilteringCounterBadge/index.test.js | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 ui/src/Components/Labels/FilteringCounterBadge/index.test.js diff --git a/ui/src/Components/Labels/FilteringCounterBadge/index.test.js b/ui/src/Components/Labels/FilteringCounterBadge/index.test.js new file mode 100644 index 000000000..45f15921d --- /dev/null +++ b/ui/src/Components/Labels/FilteringCounterBadge/index.test.js @@ -0,0 +1,112 @@ +import React from "react"; +import renderer from "react-test-renderer"; + +import { AlertStore } from "Stores/AlertStore"; + +import { FilteringCounterBadge } from "."; + +let alertStore; + +beforeEach(() => { + alertStore = new AlertStore([]); +}); + +const validateClassName = (value, className) => { + const tree = renderer + .create( + + ) + .toJSON(); + expect(tree.props.className.split(" ")).toContain(className); +}; + +const validateStyle = value => { + const tree = renderer + .create( + + ) + .toJSON(); + expect(tree.props.style).toMatchObject({}); +}; + +const validateOnClick = value => { + const tree = renderer + .create( + + ) + .toJSON(); + + tree.props.onClick({ preventDefault: () => {} }); + + expect(alertStore.filters.values).toHaveLength(1); + expect(alertStore.filters.values).toContainEqual({ + applied: false, + isValid: true, + raw: `@state=${value}`, + hits: 0, + name: "", + matcher: "", + value: "" + }); +}; + +describe("", () => { + it("@state=unprocessed counter badge should have className 'badge-secondary'", () => { + validateClassName("unprocessed", "badge-secondary"); + }); + it("@state=active counter badge should have className 'badge-secondary'", () => { + validateClassName("active", "badge-danger"); + }); + it("@state=suppressed counter badge should have className 'badge-secondary'", () => { + validateClassName("suppressed", "badge-success"); + }); + + it("@state=unprocessed counter badge should have empty style", () => { + validateStyle("unprocessed"); + }); + it("@state=active counter badge should have empty style", () => { + validateStyle("active"); + }); + it("@state=suppressed counter badge should have empty style", () => { + validateStyle("suppressed"); + }); + + it("counter badge should have correct children based on the counter prop value", () => { + const tree = renderer + .create( + + ) + .toJSON(); + expect(tree.children).toEqual(["123"]); + }); + + it("onClick method on @state=unprocessed counter badge should add a new filter", () => { + validateOnClick("unprocessed"); + }); + it("onClick method on @state=active counter badge should add a new filter", () => { + validateOnClick("active"); + }); + it("onClick method on @state=suppressed counter badge should add a new filter", () => { + validateOnClick("suppressed"); + }); +}); From 467364f3830ee9fdf84be95b9191709d5397a62f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Tue, 21 Aug 2018 20:39:09 +0100 Subject: [PATCH 2/4] refactor(ui): lower throttle to 300ms --- ui/src/Stores/AlertStore.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/Stores/AlertStore.js b/ui/src/Stores/AlertStore.js index fea8bcc69..d3674de43 100644 --- a/ui/src/Stores/AlertStore.js +++ b/ui/src/Stores/AlertStore.js @@ -212,7 +212,7 @@ class AlertStore { }); }); - fetchWithThrottle = throttle(this.fetch, 500); + fetchWithThrottle = throttle(this.fetch, 300); parseAPIResponse = action(result => { if (result.error) { From 2e36eff52e0317c01b3d3e02c8f8486692afdb55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Tue, 21 Aug 2018 20:39:32 +0100 Subject: [PATCH 3/4] fix(ui): all components should use alertStore.fetchWithThrottle() --- ui/src/Components/Fetcher/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/src/Components/Fetcher/index.js b/ui/src/Components/Fetcher/index.js index f1f85e896..e20f0332a 100644 --- a/ui/src/Components/Fetcher/index.js +++ b/ui/src/Components/Fetcher/index.js @@ -28,7 +28,7 @@ const Fetcher = observer( this.interval = newInterval; this.timer = setInterval( - () => alertStore.fetch(), + () => alertStore.fetchWithThrottle(), this.interval * 1000 ); } @@ -37,7 +37,7 @@ const Fetcher = observer( componentDidUpdate() { const { alertStore } = this.props; - alertStore.fetch(); + alertStore.fetchWithThrottle(); this.setTimer(); } @@ -45,7 +45,7 @@ const Fetcher = observer( componentDidMount() { const { alertStore } = this.props; - alertStore.fetch(); + alertStore.fetchWithThrottle(); this.setTimer(); } From 992158d7144a47cb17eabb7bd757afdd561eab69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Tue, 21 Aug 2018 22:10:42 +0100 Subject: [PATCH 4/4] feat(test): basic tests for Fetcher component --- ui/package-lock.json | 6 ++ ui/package.json | 1 + ui/src/Components/Fetcher/index.test.js | 124 ++++++++++++++++++++++++ ui/src/Stores/AlertStore.js | 24 ++--- ui/src/setupTests.js | 1 + 5 files changed, 145 insertions(+), 11 deletions(-) create mode 100644 ui/src/Components/Fetcher/index.test.js create mode 100644 ui/src/setupTests.js diff --git a/ui/package-lock.json b/ui/package-lock.json index e09004ed4..33326b5d6 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -6355,6 +6355,12 @@ "p-map": "1.2.0" } }, + "jest-localstorage-mock": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/jest-localstorage-mock/-/jest-localstorage-mock-2.2.0.tgz", + "integrity": "sha512-x+P0vcwr4540bCAYzTEpiD9rs+zh/QZzyiABV+MU6yM2OPwPlrrLyUx/6gValMyt6tg5lX6Z53o2rHWfUht5Xw==", + "dev": true + }, "jest-matcher-utils": { "version": "20.0.3", "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-20.0.3.tgz", diff --git a/ui/package.json b/ui/package.json index 372070f65..01417fb43 100644 --- a/ui/package.json +++ b/ui/package.json @@ -52,6 +52,7 @@ }, "devDependencies": { "eslint-plugin-react": "7.11.1", + "jest-localstorage-mock": "2.2.0", "markdownlint-cli": "0.13.0", "node-sass-chokidar": "1.3.3", "onchange": "4.1.0", diff --git a/ui/src/Components/Fetcher/index.test.js b/ui/src/Components/Fetcher/index.test.js new file mode 100644 index 000000000..02c2c7cfa --- /dev/null +++ b/ui/src/Components/Fetcher/index.test.js @@ -0,0 +1,124 @@ +import React from "react"; +import renderer from "react-test-renderer"; + +import { FetchMock, EmptyAPIResponse } from "__mocks__/Fetch"; + +import { AlertStore } from "Stores/AlertStore"; +import { Settings } from "Stores/Settings"; + +import { Fetcher } from "."; + +beforeAll(() => { + jest.useFakeTimers(); +}); + +let alertStore; +let settingsStore; + +beforeEach(() => { + const response = EmptyAPIResponse(); + global.fetch = FetchMock(response); + + alertStore = new AlertStore(["label=value"]); + settingsStore = new Settings(); +}); + +afterEach(() => { + jest.clearAllTimers(); + + global.fetch.mockRestore(); +}); + +describe("", () => { + it("renders correctly with 'label=value' filter", () => { + const tree = renderer + .create() + .toJSON(); + + expect(tree.props["data-filters"]).toBe("label=value"); + expect(tree.props["data-interval"]).toBe(30); + }); + + it("re-renders on fetch interval change", () => { + const fetcher = renderer.create( + + ); + + expect(fetcher.toJSON().props["data-interval"]).toBe(30); + settingsStore.fetchConfig.config.interval = 60; + expect(fetcher.toJSON().props["data-interval"]).toBe(60); + }); + + it("re-renders on filters change", () => { + const fetcher = renderer.create( + + ); + + expect(fetcher.toJSON().props["data-filters"]).toBe("label=value"); + alertStore.filters.values = []; + expect(fetcher.toJSON().props["data-filters"]).toBe(""); + }); + + it("calls alertStore.fetchWithThrottle on mount", () => { + const fetchSpy = jest.spyOn(alertStore, "fetchWithThrottle"); + + renderer.create( + + ); + + expect(fetchSpy).toHaveBeenCalledTimes(1); + }); + + it("calls alertStore.fetchWithThrottle again after interval change", () => { + const fetchSpy = jest.spyOn(alertStore, "fetchWithThrottle"); + + renderer.create( + + ); + settingsStore.fetchConfig.config.interval = 60; + + expect(fetchSpy).toHaveBeenCalledTimes(2); + }); + + it("calls alertStore.fetchWithThrottle again after filter change", () => { + const fetchSpy = jest.spyOn(alertStore, "fetchWithThrottle"); + + renderer.create( + + ); + alertStore.filters.values = []; + + expect(fetchSpy).toHaveBeenCalledTimes(2); + }); + + it("keeps calling alertStore.fetchWithThrottle after running pending timers", () => { + const fetchSpy = jest.spyOn(alertStore, "fetchWithThrottle"); + + renderer.create( + + ); + jest.runOnlyPendingTimers(); + expect(fetchSpy).toHaveBeenCalledTimes(2); + jest.runOnlyPendingTimers(); + expect(fetchSpy).toHaveBeenCalledTimes(3); + jest.runOnlyPendingTimers(); + expect(fetchSpy).toHaveBeenCalledTimes(4); + }); + + it("internal timer is armed after render", () => { + const fetcher = renderer.create( + + ); + const instance = fetcher.getInstance(); + expect(instance.timer).toBeGreaterThanOrEqual(0); + }); + + it("internal timer is null after unmount", () => { + const fetcher = renderer.create( + + ); + const instance = fetcher.getInstance(); + instance.componentWillUnmount(); + expect(instance.timer).toBeNull(); + }); +}); diff --git a/ui/src/Stores/AlertStore.js b/ui/src/Stores/AlertStore.js index d3674de43..419ebd4e1 100644 --- a/ui/src/Stores/AlertStore.js +++ b/ui/src/Stores/AlertStore.js @@ -220,18 +220,20 @@ class AlertStore { return; } - const queryFilters = new Set( - this.filters.values - .map(f => f.raw) - .slice() - .sort() - ); - const responseFilters = new Set(result.filters.map(m => m.text).sort()); - if ( - JSON.stringify([...queryFilters]) !== JSON.stringify([...responseFilters]) - ) { + const queryFilters = [ + ...new Set( + this.filters.values + .map(f => f.raw) + .slice() + .sort() + ) + ]; + const responseFilters = [ + ...new Set(result.filters.map(m => m.text).sort()) + ]; + if (JSON.stringify(queryFilters) !== JSON.stringify(responseFilters)) { console.info( - `Got response with filters=${responseFilters} while expecting results for ${queryFilters}, ignoring` + `Got response with filters '${responseFilters}' while expecting results for '${queryFilters}', ignoring` ); return; } diff --git a/ui/src/setupTests.js b/ui/src/setupTests.js new file mode 100644 index 000000000..5b9840092 --- /dev/null +++ b/ui/src/setupTests.js @@ -0,0 +1 @@ +require("jest-localstorage-mock");