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");