From 40694674357e882e9d53a39e08af25aad7632ab4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Wed, 22 Aug 2018 12:22:42 +0100 Subject: [PATCH 1/7] fix(ui): don't use new lines for rgba() style values --- ui/src/Components/Labels/BaseLabel/index.js | 24 ++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/ui/src/Components/Labels/BaseLabel/index.js b/ui/src/Components/Labels/BaseLabel/index.js index 3f6f79138..12d2ed95c 100644 --- a/ui/src/Components/Labels/BaseLabel/index.js +++ b/ui/src/Components/Labels/BaseLabel/index.js @@ -43,18 +43,18 @@ class BaseLabel extends Component { alertStore.data.colors[name][value] !== undefined ) { const c = alertStore.data.colors[name][value]; - style["backgroundColor"] = `rgba( - ${c.background.red}, - ${c.background.green}, - ${c.background.blue}, - ${c.background.alpha} - )`; - style["color"] = `rgba( - ${c.font.red}, - ${c.font.green}, - ${c.font.blue}, - ${c.font.alpha} - )`; + style["backgroundColor"] = `rgba(${[ + c.background.red, + c.background.green, + c.background.blue, + c.background.alpha + ].join(", ")})`; + style["color"] = `rgba(${[ + c.font.red, + c.font.green, + c.font.blue, + c.font.alpha + ].join(", ")})`; } return style; } From 56a2a147ca1ea2f0665817daa1943fee18f3f0f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Wed, 22 Aug 2018 12:27:07 +0100 Subject: [PATCH 2/7] feat(test): add BaseLabel tests --- .../Components/Labels/BaseLabel/index.test.js | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 ui/src/Components/Labels/BaseLabel/index.test.js diff --git a/ui/src/Components/Labels/BaseLabel/index.test.js b/ui/src/Components/Labels/BaseLabel/index.test.js new file mode 100644 index 000000000..dc86bff0c --- /dev/null +++ b/ui/src/Components/Labels/BaseLabel/index.test.js @@ -0,0 +1,80 @@ +import React from "react"; +import renderer from "react-test-renderer"; + +import { AlertStore } from "Stores/AlertStore"; + +import { BaseLabel } from "."; + +let alertStore; + +beforeEach(() => { + alertStore = new AlertStore([]); +}); + +const FakeBaseLabel = () => { + // BaseLabel doesn't implement render since it's an abstract component + // Add a dummy implementation for testing + class RenderableBaseLabel extends BaseLabel { + render() { + return null; + } + } + return renderer.create( + + ); +}; + +describe("", () => { + it("isStaticColorLabel() returns true for labels present in staticColorLabels", () => { + alertStore.settings.values.staticColorLabels = ["foo", "job", "bar"]; + const instance = FakeBaseLabel().getInstance(); + expect(instance.isStaticColorLabel("job")).toBeTruthy(); + }); + + it("isStaticColorLabel() returns false for labels not present in staticColorLabels", () => { + alertStore.settings.values.staticColorLabels = ["foo"]; + const instance = FakeBaseLabel().getInstance(); + expect(instance.isStaticColorLabel("job")).toBeFalsy(); + }); + + it("getColorClass() on a label included in staticColorLabels should return 'info'", () => { + alertStore.settings.values.staticColorLabels = ["job"]; + const instance = FakeBaseLabel().getInstance(); + expect(instance.getColorClass("job", "foo")).toBe("info"); + }); + + it("getColorClass() on a label without any special color should return 'warning'", () => { + const instance = FakeBaseLabel().getInstance(); + expect(instance.getColorClass("foo", "bar")).toBe("warning"); + }); + + it("getColorClass() on 'alertname' label should return 'dark'", () => { + const instance = FakeBaseLabel().getInstance(); + expect(instance.getColorClass("alertname", "foo")).toBe("dark"); + }); + + it("getColorStyle() on a label included in staticColorLabels should be empty", () => { + alertStore.settings.values.staticColorLabels = ["job"]; + const instance = FakeBaseLabel().getInstance(); + expect(instance.getColorStyle("job", "bar")).toMatchObject({}); + }); + + it("getColorStyle() on a label without any color information should be empty", () => { + const instance = FakeBaseLabel().getInstance(); + expect(instance.getColorStyle("foo", "bar")).toMatchObject({}); + }); + + it("getColorStyle() on a label with color information should be correctly formatted", () => { + alertStore.data.colors["foo"] = { + bar: { + font: { red: 1, green: 2, blue: 3, alpha: 100 }, + background: { red: 4, green: 5, blue: 6, alpha: 200 } + } + }; + const instance = FakeBaseLabel().getInstance(); + expect(instance.getColorStyle("foo", "bar")).toMatchObject({ + color: "rgba(1, 2, 3, 100)", + backgroundColor: "rgba(4, 5, 6, 200)" + }); + }); +}); From 53cb95b197ba1f35f57d3c8463562c043d36e820 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Wed, 22 Aug 2018 15:24:49 +0100 Subject: [PATCH 3/7] fix(tests): drop --onlyChanged Using --onlyChanged means that coverage will only include a subset of tests, so it won't be accurate --- Makefile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Makefile b/Makefile index e6bb8959f..d40b0c01b 100644 --- a/Makefile +++ b/Makefile @@ -133,8 +133,7 @@ test-js-watch: .build/deps-build-node.ok @# https://github.com/facebook/jest/issues/3436 @# use onchange for now cd ui && ./node_modules/onchange/cli.js 'src/*.js' 'src/**/*.js' -- \ - npm test -- \ - --coverage --coverageReporters=lcov --onlyChanged + npm test -- --coverage --coverageReporters=lcov .PHONY: test test: lint test-go test-js From 99f5f3999ead4a30ffd8270b8ce29a8f57e3acf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Wed, 22 Aug 2018 15:34:58 +0100 Subject: [PATCH 4/7] refactor(tests): use jest-fetch-mock for mocking fetch() calls --- ui/package-lock.json | 23 +++++++++++++++++++ ui/package.json | 1 + ui/src/Components/Fetcher/index.test.js | 5 ++--- ui/src/Stores/AlertStore.test.js | 30 +++++-------------------- ui/src/__mocks__/Fetch.js | 9 +------- ui/src/setupTests.js | 5 +++++ 6 files changed, 38 insertions(+), 35 deletions(-) diff --git a/ui/package-lock.json b/ui/package-lock.json index 33326b5d6..99f51173b 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -133,6 +133,12 @@ "prop-types": "15.6.2" } }, + "@types/jest": { + "version": "23.3.1", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-23.3.1.tgz", + "integrity": "sha512-/UMY+2GkOZ27Vrc51pqC5J8SPd39FKt7kkoGAtWJ8s4msj0b15KehDWIiJpWY3/7tLxBQLLzJhIBhnEsXdzpgw==", + "dev": true + }, "abab": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/abab/-/abab-1.0.4.tgz", @@ -6257,6 +6263,17 @@ "jest-util": "20.0.3" } }, + "jest-fetch-mock": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/jest-fetch-mock/-/jest-fetch-mock-1.6.5.tgz", + "integrity": "sha512-qPz5Zf8+W16pu6cvdwXkb2SwRfxGoQbbGB6HcIBFND0gnWKMfQilZew3PSODnOWQZF/pzBPi7ZIT6Yz5D0va1Q==", + "dev": true, + "requires": { + "@types/jest": "23.3.1", + "isomorphic-fetch": "2.2.1", + "promise-polyfill": "7.1.2" + } + }, "jest-haste-map": { "version": "20.0.5", "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-20.0.5.tgz", @@ -9611,6 +9628,12 @@ "asap": "2.0.6" } }, + "promise-polyfill": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-7.1.2.tgz", + "integrity": "sha512-FuEc12/eKqqoRYIGBrUptCBRhobL19PS2U31vMNTfyck1FxPyMfgsXyW4Mav85y/ZN1hop3hOwRlUDok23oYfQ==", + "dev": true + }, "prop-types": { "version": "15.6.2", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", diff --git a/ui/package.json b/ui/package.json index 01417fb43..1ad7284ff 100644 --- a/ui/package.json +++ b/ui/package.json @@ -52,6 +52,7 @@ }, "devDependencies": { "eslint-plugin-react": "7.11.1", + "jest-fetch-mock": "1.6.5", "jest-localstorage-mock": "2.2.0", "markdownlint-cli": "0.13.0", "node-sass-chokidar": "1.3.3", diff --git a/ui/src/Components/Fetcher/index.test.js b/ui/src/Components/Fetcher/index.test.js index 02c2c7cfa..a7c9f71c4 100644 --- a/ui/src/Components/Fetcher/index.test.js +++ b/ui/src/Components/Fetcher/index.test.js @@ -1,7 +1,7 @@ import React from "react"; import renderer from "react-test-renderer"; -import { FetchMock, EmptyAPIResponse } from "__mocks__/Fetch"; +import { EmptyAPIResponse } from "__mocks__/Fetch"; import { AlertStore } from "Stores/AlertStore"; import { Settings } from "Stores/Settings"; @@ -16,8 +16,7 @@ let alertStore; let settingsStore; beforeEach(() => { - const response = EmptyAPIResponse(); - global.fetch = FetchMock(response); + fetch.mockResponse(JSON.stringify(EmptyAPIResponse())); alertStore = new AlertStore(["label=value"]); settingsStore = new Settings(); diff --git a/ui/src/Stores/AlertStore.test.js b/ui/src/Stores/AlertStore.test.js index 67bd24a4a..8e155a59d 100644 --- a/ui/src/Stores/AlertStore.test.js +++ b/ui/src/Stores/AlertStore.test.js @@ -1,5 +1,5 @@ import { ConsoleMock } from "__mocks__/Console"; -import { FetchMock, EmptyAPIResponse } from "__mocks__/Fetch"; +import { EmptyAPIResponse } from "__mocks__/Fetch"; import { AlertStore, @@ -9,12 +9,11 @@ import { } from "Stores/AlertStore"; beforeEach(() => { - // wipe REACT_APP_BACKEND_URI env on each run as it's used by some tests - delete process.env.REACT_APP_BACKEND_URI; + fetch.resetMocks(); }); afterEach(() => { - // same after each + // wipe REACT_APP_BACKEND_URI env on each run as it's used by some tests delete process.env.REACT_APP_BACKEND_URI; }); @@ -221,7 +220,7 @@ describe("AlertStore.fetch", () => { it("fetch() works with valid response", async () => { const response = EmptyAPIResponse(); - global.fetch = FetchMock(response); + fetch.mockResponse(JSON.stringify(response)); const store = new AlertStore(["label=value"]); await expect(store.fetch()).resolves.toBeUndefined(); @@ -229,18 +228,10 @@ describe("AlertStore.fetch", () => { expect(global.fetch).toHaveBeenCalledTimes(1); expect(store.status.value).toEqual(AlertStoreStatuses.Idle); expect(store.info.version).toBe("fakeVersion"); - - global.fetch.mockRestore(); }); it("fetch() handles response with error correctly", async () => { - global.fetch = jest.fn().mockImplementation(() => - Promise.resolve({ - json: () => ({ - error: "Fetch error" - }) - }) - ); + fetch.mockResponse(JSON.stringify({ error: "Fetch error" })); const store = new AlertStore([]); await expect(store.fetch()).resolves.toBeUndefined(); @@ -248,19 +239,11 @@ describe("AlertStore.fetch", () => { expect(global.fetch).toHaveBeenCalledTimes(1); expect(store.status.value).toEqual(AlertStoreStatuses.Failure); expect(store.info.version).toBe("unknown"); - - global.fetch.mockRestore(); }); it("fetch() handles response that throws an error correctly", async () => { const consoleSpy = ConsoleMock("trace"); - global.fetch = jest.fn().mockImplementation(() => - Promise.resolve({ - json: () => { - throw new Error("Failed fetch"); - } - }) - ); + fetch.mockReject("Fetch error"); const store = new AlertStore([]); await expect(store.fetch()).resolves.toHaveProperty("error"); @@ -272,6 +255,5 @@ describe("AlertStore.fetch", () => { expect(consoleSpy).toHaveBeenCalledTimes(1); consoleSpy.mockRestore(); - global.fetch.mockRestore(); }); }); diff --git a/ui/src/__mocks__/Fetch.js b/ui/src/__mocks__/Fetch.js index add9b7a23..89675a104 100644 --- a/ui/src/__mocks__/Fetch.js +++ b/ui/src/__mocks__/Fetch.js @@ -1,12 +1,5 @@ import moment from "moment"; -const FetchMock = data => - jest.fn().mockImplementation(() => - Promise.resolve({ - json: () => data - }) - ); - const EmptyAPIResponse = () => ({ status: "success", timestamp: moment().toISOString(), @@ -38,4 +31,4 @@ const EmptyAPIResponse = () => ({ } }); -export { FetchMock, EmptyAPIResponse }; +export { EmptyAPIResponse }; diff --git a/ui/src/setupTests.js b/ui/src/setupTests.js index 5b9840092..8c50a6876 100644 --- a/ui/src/setupTests.js +++ b/ui/src/setupTests.js @@ -1 +1,6 @@ +// localStorage is used for Settings store require("jest-localstorage-mock"); + +// fetch is used in multiple places to interact with Go backend +// or upstream Alertmanager API +global.fetch = require("jest-fetch-mock"); From 210da0a5ba17640464fd2395240634e15301a5e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Wed, 22 Aug 2018 15:46:03 +0100 Subject: [PATCH 5/7] refactor(tests): use jest-mock-console for mocking console calls --- ui/package-lock.json | 6 ++++++ ui/package.json | 1 + ui/src/Stores/AlertStore.test.js | 7 +++---- ui/src/__mocks__/Console.js | 4 ---- ui/src/setupTests.js | 5 +++++ 5 files changed, 15 insertions(+), 8 deletions(-) delete mode 100644 ui/src/__mocks__/Console.js diff --git a/ui/package-lock.json b/ui/package-lock.json index 99f51173b..447525f37 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -6482,6 +6482,12 @@ "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-20.0.3.tgz", "integrity": "sha1-i8Bw6QQUqhVcEajWTIaaDVxx2lk=" }, + "jest-mock-console": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/jest-mock-console/-/jest-mock-console-0.4.0.tgz", + "integrity": "sha512-WElCbNvfqQlD7cpfHfTn1ytZ+RjKg1Ftrvr5wEjdWP7a9esXmaiZuEAPeYUSK5fd0Cra+dR1oF8HAjjKKxDQdg==", + "dev": true + }, "jest-regex-util": { "version": "20.0.3", "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-20.0.3.tgz", diff --git a/ui/package.json b/ui/package.json index 1ad7284ff..bc90649da 100644 --- a/ui/package.json +++ b/ui/package.json @@ -54,6 +54,7 @@ "eslint-plugin-react": "7.11.1", "jest-fetch-mock": "1.6.5", "jest-localstorage-mock": "2.2.0", + "jest-mock-console": "0.4.0", "markdownlint-cli": "0.13.0", "node-sass-chokidar": "1.3.3", "onchange": "4.1.0", diff --git a/ui/src/Stores/AlertStore.test.js b/ui/src/Stores/AlertStore.test.js index 8e155a59d..0e9dd80d3 100644 --- a/ui/src/Stores/AlertStore.test.js +++ b/ui/src/Stores/AlertStore.test.js @@ -1,4 +1,3 @@ -import { ConsoleMock } from "__mocks__/Console"; import { EmptyAPIResponse } from "__mocks__/Fetch"; import { @@ -192,7 +191,7 @@ describe("DecodeLocationSearch", () => { describe("AlertStore.fetch", () => { it("parseAPIResponse() rejects a response with mismatched filters", () => { - const consoleSpy = ConsoleMock("info"); + const consoleSpy = jest.spyOn(console, "info"); const response = EmptyAPIResponse(); const store = new AlertStore([]); @@ -202,7 +201,7 @@ describe("AlertStore.fetch", () => { // there should be no filters set on AlertStore instance since we started // with 0 and rejected response with 1 filter expect(store.filters.values).toHaveLength(0); - // console.info should have been called since we emited a warning + // console.info should have been called since we emited a log line expect(consoleSpy).toHaveBeenCalledTimes(1); consoleSpy.mockRestore(); @@ -242,7 +241,7 @@ describe("AlertStore.fetch", () => { }); it("fetch() handles response that throws an error correctly", async () => { - const consoleSpy = ConsoleMock("trace"); + const consoleSpy = jest.spyOn(console, "trace"); fetch.mockReject("Fetch error"); const store = new AlertStore([]); diff --git a/ui/src/__mocks__/Console.js b/ui/src/__mocks__/Console.js deleted file mode 100644 index a7dfdef42..000000000 --- a/ui/src/__mocks__/Console.js +++ /dev/null @@ -1,4 +0,0 @@ -const ConsoleMock = level => - jest.spyOn(console, level).mockImplementation(() => jest.fn()); - -export { ConsoleMock }; diff --git a/ui/src/setupTests.js b/ui/src/setupTests.js index 8c50a6876..6c3236b3f 100644 --- a/ui/src/setupTests.js +++ b/ui/src/setupTests.js @@ -1,3 +1,8 @@ +import mockConsole from "jest-mock-console"; + +// mock console +mockConsole(["error", "warn", "info", "log", "trace"]); + // localStorage is used for Settings store require("jest-localstorage-mock"); From 7d5e957453070bf7db6f9d2dc33df675b0bef126 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Wed, 22 Aug 2018 16:22:07 +0100 Subject: [PATCH 6/7] refactor(tests): dedup some code, add more label test coverage --- .../Labels/FilterInputLabel/index.test.js | 23 +++++++++ .../FilteringCounterBadge/index.test.js | 14 ++---- .../Labels/FilteringLabel/index.test.js | 50 +++++++++++++++++++ ui/src/Stores/AlertStore.js | 11 ++-- 4 files changed, 83 insertions(+), 15 deletions(-) create mode 100644 ui/src/Components/Labels/FilterInputLabel/index.test.js create mode 100644 ui/src/Components/Labels/FilteringLabel/index.test.js diff --git a/ui/src/Components/Labels/FilterInputLabel/index.test.js b/ui/src/Components/Labels/FilterInputLabel/index.test.js new file mode 100644 index 000000000..464c25fde --- /dev/null +++ b/ui/src/Components/Labels/FilterInputLabel/index.test.js @@ -0,0 +1,23 @@ +import React from "react"; +import renderer from "react-test-renderer"; + +import { AlertStore, NewUnappliedFilter } from "Stores/AlertStore"; + +import { FilterInputLabel } from "."; + +let alertStore; + +beforeEach(() => { + alertStore = new AlertStore([]); +}); + +describe("", () => { + it("renders without crashing", () => { + renderer.create( + + ); + }); +}); diff --git a/ui/src/Components/Labels/FilteringCounterBadge/index.test.js b/ui/src/Components/Labels/FilteringCounterBadge/index.test.js index 45f15921d..4ee1755d8 100644 --- a/ui/src/Components/Labels/FilteringCounterBadge/index.test.js +++ b/ui/src/Components/Labels/FilteringCounterBadge/index.test.js @@ -1,7 +1,7 @@ import React from "react"; import renderer from "react-test-renderer"; -import { AlertStore } from "Stores/AlertStore"; +import { AlertStore, NewUnappliedFilter } from "Stores/AlertStore"; import { FilteringCounterBadge } from "."; @@ -54,15 +54,9 @@ const validateOnClick = value => { 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: "" - }); + expect(alertStore.filters.values).toContainEqual( + NewUnappliedFilter(`@state=${value}`) + ); }; describe("", () => { diff --git a/ui/src/Components/Labels/FilteringLabel/index.test.js b/ui/src/Components/Labels/FilteringLabel/index.test.js new file mode 100644 index 000000000..c7e6e543d --- /dev/null +++ b/ui/src/Components/Labels/FilteringLabel/index.test.js @@ -0,0 +1,50 @@ +import React from "react"; +import renderer from "react-test-renderer"; + +import { AlertStore, NewUnappliedFilter } from "Stores/AlertStore"; + +import { FilteringLabel } from "."; + +let alertStore; + +beforeEach(() => { + alertStore = new AlertStore([]); +}); + +const RenderAndClick = (name, value) => { + const tree = renderer + .create( + + ) + .toJSON(); + + tree.props.onClick({ preventDefault: () => {} }); +}; + +describe("", () => { + it("renders without crashing", () => { + renderer.create( + + ); + }); + + it("calling onClick() adds a new filter 'foo=bar'", () => { + RenderAndClick("foo", "bar"); + expect(alertStore.filters.values).toHaveLength(1); + expect(alertStore.filters.values).toContainEqual( + NewUnappliedFilter("foo=bar") + ); + }); + + it("calling onClick() multiple times appends extra filter 'baz=bar'", () => { + RenderAndClick("foo", "bar"); + RenderAndClick("bar", "baz"); + expect(alertStore.filters.values).toHaveLength(2); + expect(alertStore.filters.values).toContainEqual( + NewUnappliedFilter("foo=bar") + ); + expect(alertStore.filters.values).toContainEqual( + NewUnappliedFilter("bar=baz") + ); + }); +}); diff --git a/ui/src/Stores/AlertStore.js b/ui/src/Stores/AlertStore.js index 419ebd4e1..9166d85db 100644 --- a/ui/src/Stores/AlertStore.js +++ b/ui/src/Stores/AlertStore.js @@ -71,7 +71,7 @@ const AlertStoreStatuses = Object.freeze({ Failure: Symbol("failure") }); -function newUnappliedFilter(raw) { +function NewUnappliedFilter(raw) { return { applied: false, isValid: true, @@ -89,7 +89,7 @@ class AlertStore { values: [], addFilter(raw) { if (this.values.filter(f => f.raw === raw).length === 0) { - this.values.push(newUnappliedFilter(raw)); + this.values.push(NewUnappliedFilter(raw)); UpdateLocationSearch({ q: this.values.map(f => f.raw) }); } }, @@ -108,13 +108,13 @@ class AlertStore { this.removeFilter(oldRaw); } else { // no dups, continue with a swap - this.values[index] = newUnappliedFilter(newRaw); + this.values[index] = NewUnappliedFilter(newRaw); UpdateLocationSearch({ q: this.values.map(f => f.raw) }); } } }, setFilters(raws) { - this.values = raws.map(raw => newUnappliedFilter(raw)); + this.values = raws.map(raw => NewUnappliedFilter(raw)); UpdateLocationSearch({ q: this.values.map(f => f.raw) }); } }, @@ -318,5 +318,6 @@ export { AlertStoreStatuses, FormatUnseeBackendURI, FormatAPIFilterQuery, - DecodeLocationSearch + DecodeLocationSearch, + NewUnappliedFilter }; From b4cbb0d8d5872c75b12897752c3b3e3bc672592b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Wed, 22 Aug 2018 19:32:05 +0100 Subject: [PATCH 7/7] feat(tests): all label classes are now tested --- .../Labels/FilterInputLabel/index.test.js | 169 +++++++++++++++++- 1 file changed, 165 insertions(+), 4 deletions(-) diff --git a/ui/src/Components/Labels/FilterInputLabel/index.test.js b/ui/src/Components/Labels/FilterInputLabel/index.test.js index 464c25fde..0f5e19c92 100644 --- a/ui/src/Components/Labels/FilterInputLabel/index.test.js +++ b/ui/src/Components/Labels/FilterInputLabel/index.test.js @@ -11,13 +11,174 @@ beforeEach(() => { alertStore = new AlertStore([]); }); -describe("", () => { - it("renders without crashing", () => { - renderer.create( +const NonEqualMatchers = ["!=", "=~", "!~", ">", "<"]; + +const MockColors = () => { + alertStore.data.colors["foo"] = { + bar: { + font: { red: 1, green: 2, blue: 3, alpha: 100 }, + background: { red: 4, green: 5, blue: 6, alpha: 200 } + } + }; +}; + +const FakeLabel = (matcher, applied) => { + const name = "foo"; + const value = "bar"; + const filter = NewUnappliedFilter(`${name}${matcher}${value}`); + filter.applied = applied; + filter.name = name; + filter.matcher = matcher; + filter.value = value; + return renderer.create( + + ); +}; + +const ValidateClass = (matcher, applied, expectedClass) => { + const tree = FakeLabel(matcher, applied).toJSON(); + expect(tree.props.className.split(" ")).toContain(expectedClass); +}; + +const ValidateOnChange = newRaw => { + const component = renderer.create( + + ); + const tree = component.toTree(); + + // call onChange with new raw value + tree.instance.onChange({ raw: newRaw }); + + return tree; +}; + +describe(" className", () => { + it("unapplied filter with '=' matcher should use 'badge-secondary' class", () => { + ValidateClass("=", false, "badge-secondary"); + }); + + it("unapplied filter with any matcher other than '=' should use 'badge-secondary' class", () => { + for (const matcher of NonEqualMatchers) { + ValidateClass(matcher, false, "badge-secondary"); + } + }); + + it("applied filter with '=' matcher and no color should use 'badge-warning' class", () => { + ValidateClass("=", true, "badge-warning"); + }); + + it("applied filter with any matcher other than '=' and no color should use 'badge-warning' class", () => { + for (const matcher of NonEqualMatchers) { + ValidateClass(matcher, true, "badge-warning"); + } + }); + + it("applied filter included in staticColorLabels with '=' matcher should use 'badge-info' class", () => { + alertStore.settings.values.staticColorLabels = ["foo"]; + ValidateClass("=", true, "badge-info"); + }); + + it("applied filter included in staticColorLabels with any matcher other than '=' should use 'badge-warning' class", () => { + alertStore.settings.values.staticColorLabels = ["foo"]; + for (const matcher of NonEqualMatchers) { + ValidateClass(matcher, true, "badge-warning"); + } + }); +}); + +describe(" style", () => { + it("unapplied filter with color information and '=' matcher should have empty style", () => { + MockColors(); + const tree = FakeLabel("=", false).toJSON(); + expect(tree.props.style).toMatchObject({}); + }); + + it("unapplied filter with no color information and '=' matcher should have empty style", () => { + const tree = FakeLabel("=", false).toJSON(); + expect(tree.props.style).toMatchObject({}); + }); + + it("unapplied filter with no color information and any matcher other than '=' should have empty style", () => { + for (const matcher of NonEqualMatchers) { + const tree = FakeLabel(matcher, false).toJSON(); + expect(tree.props.style).toMatchObject({}); + } + }); + + it("applied filter with color information and '=' matcher should have non empty style", () => { + MockColors(); + const tree = FakeLabel("=", true).toJSON(); + expect(tree.props.style).toMatchObject({ + color: "rgba(1, 2, 3, 100)", + backgroundColor: "rgba(4, 5, 6, 200)" + }); + }); + + it("applied filter with no color information and '=' matcher should have empty style", () => { + const tree = FakeLabel("=", true).toJSON(); + expect(tree.props.style).toMatchObject({}); + }); + + it("applied filter with no color information and any matcher other than '=' should have empty style", () => { + for (const matcher of NonEqualMatchers) { + const tree = FakeLabel(matcher, true).toJSON(); + expect(tree.props.style).toMatchObject({}); + } + }); +}); + +describe(" onChange", () => { + it("filter raw value is updated after onChange call", () => { + alertStore.filters.values = [NewUnappliedFilter("foo=bar")]; + ValidateOnChange("baz=abc"); + expect(alertStore.filters.values).toHaveLength(1); + expect(alertStore.filters.values).toContainEqual( + NewUnappliedFilter("baz=abc") + ); + }); + + it("filter is removed after onChange call with empty value", () => { + alertStore.filters.values = [NewUnappliedFilter("foo=bar")]; + ValidateOnChange(""); + expect(alertStore.filters.values).toHaveLength(0); + }); + + it("onChange doesn't allow duplicates", () => { + alertStore.filters.values = [ + NewUnappliedFilter("foo=bar"), + NewUnappliedFilter("bar=baz") + ]; + ValidateOnChange("bar=baz"); + expect(alertStore.filters.values).toHaveLength(1); + expect(alertStore.filters.values).not.toContainEqual( + NewUnappliedFilter("foo=bar") + ); + expect(alertStore.filters.values).toContainEqual( + NewUnappliedFilter("bar=baz") + ); + }); +}); + +describe(" onChange", () => { + it("clicking on the X button removes filters from alertStore", () => { + alertStore.filters.values = [ + NewUnappliedFilter("foo=bar"), + NewUnappliedFilter("bar=baz") + ]; + const component = renderer.create( + ); + const button = component.root.findByType("button"); + button.props.onClick(); + expect(alertStore.filters.values).toHaveLength(1); + expect(alertStore.filters.values).toContainEqual( + NewUnappliedFilter("bar=baz") ); }); });