components/frontend/src/metric/MetricDetails.test.js
import { act, fireEvent, render, screen } from "@testing-library/react"
import history from "history/browser"
import { createTestableSettings } from "../__fixtures__/fixtures"
import * as changelog_api from "../api/changelog"
import * as fetch_server_api from "../api/fetch_server_api"
import * as measurement_api from "../api/measurement"
import * as source_api from "../api/source"
import { DataModel } from "../context/DataModel"
import { EDIT_ENTITY_PERMISSION, EDIT_REPORT_PERMISSION, Permissions } from "../context/Permissions"
import { MetricDetails } from "./MetricDetails"
jest.mock("../api/fetch_server_api")
jest.mock("../api/changelog.js")
jest.mock("../api/measurement.js")
jest.mock("../api/source.js")
const report = {
report_uuid: "report_uuid",
subjects: {
subject_uuid: {
name: "Metric",
type: "subject_type",
metrics: {
metric_uuid: {
accept_debt: false,
tags: [],
type: "violations",
sources: {
source_uuid: {
type: "source_type",
entities: [],
},
},
},
metric_uuid2: {},
},
},
},
}
const dataModel = {
sources: {
source_type: {
name: "The source",
parameters: {},
parameter_layout: {
all: {
name: "All parameters",
parameters: [],
},
},
entities: { violations: { name: "Attribute", attributes: [] } },
},
},
metrics: { violations: { direction: "<", tags: [], sources: ["source_type"] } },
subjects: { subject_type: { metrics: ["violations"] } },
}
async function renderMetricDetails(stopFilteringAndSorting, connection_error, failLoadingMeasurements) {
measurement_api.get_metric_measurements.mockImplementation(() => {
if (failLoadingMeasurements) {
return Promise.reject(new Error("failed to load measurements"))
} else {
return Promise.resolve({
measurements: [
{
count: { value: "42" },
version_number: { value: "1.1" },
start: "2020-02-29T10:25:52.252Z",
end: "2020-02-29T11:25:52.252Z",
sources: [
{},
{ source_uuid: "source_uuid2" },
{
source_uuid: "source_uuid",
entities: [{ key: "1" }],
connection_error: connection_error,
},
],
},
],
})
}
})
source_api.set_source_entity_attribute.mockImplementation(
(_metric_uuid, _source_uuid, _entity_key, _attribute, _value, reload) => reload(),
)
changelog_api.get_changelog.mockImplementation(() => Promise.resolve({ changelog: [] }))
const settings = createTestableSettings()
await act(async () =>
render(
<Permissions.Provider value={[EDIT_ENTITY_PERMISSION, EDIT_REPORT_PERMISSION]}>
<DataModel.Provider value={dataModel}>
<MetricDetails
metric_uuid="metric_uuid"
reload={jest.fn()}
report={report}
reports={[report]}
stopFilteringAndSorting={stopFilteringAndSorting}
subject_uuid="subject_uuid"
expandedItems={settings.expandedItems}
/>
</DataModel.Provider>
</Permissions.Provider>,
),
)
}
beforeEach(() => {
jest.clearAllMocks()
history.push("")
fetch_server_api.fetch_server_api.mockImplementation(() => Promise.resolve())
})
it("switches tabs", async () => {
await renderMetricDetails()
expect(screen.getAllByText(/Metric name/).length).toBe(1)
fireEvent.click(screen.getByText(/Sources/))
expect(screen.getAllByText(/Source name/).length).toBe(1)
})
it("switches tabs to technical debt", async () => {
await renderMetricDetails()
expect(screen.getAllByText(/Metric name/).length).toBe(1)
fireEvent.click(screen.getByText(/Technical debt/))
expect(screen.getAllByText(/Technical debt target/).length).toBe(1)
})
it("switches tabs to measurement entities", async () => {
await renderMetricDetails()
expect(screen.getAllByText(/Metric name/).length).toBe(1)
fireEvent.click(screen.getByText(/The source/))
expect(screen.getAllByText(/Attribute status/).length).toBe(1)
})
it("switches tabs to the trend graph", async () => {
await renderMetricDetails()
expect(screen.getAllByText(/Metric name/).length).toBe(1)
fireEvent.click(screen.getByText(/Trend graph/))
expect(screen.getAllByText(/Time/).length).toBe(1)
})
it("shows the trend graph tab even if the metric scale is version number", async () => {
report.subjects["subject_uuid"].metrics["metric_uuid"].scale = "version_number"
await renderMetricDetails()
expect(screen.queryAllByText(/Trend graph/).length).toBe(1)
report.subjects["subject_uuid"].metrics["metric_uuid"].scale = "count"
})
it("removes the existing hashtag from the URL to share", async () => {
history.push("#hash_that_should_be_removed")
Object.assign(window, { isSecureContext: true })
Object.assign(navigator, {
clipboard: { writeText: jest.fn().mockImplementation(() => Promise.resolve()) },
})
await renderMetricDetails()
await act(async () => {
fireEvent.click(screen.getByText(/Share/))
})
expect(navigator.clipboard.writeText).toHaveBeenCalledWith("http://localhost/#metric_uuid")
})
it("displays whether sources have errors", async () => {
await renderMetricDetails(null, "Connection error")
expect(screen.getByText(/Sources/)).toHaveClass("red label")
})
it("moves the metric", async () => {
const mockCallback = jest.fn()
await renderMetricDetails(mockCallback)
await act(async () => fireEvent.click(screen.getByLabelText(/Move metric to the last row/)))
expect(mockCallback).toHaveBeenCalled()
expect(measurement_api.get_metric_measurements).toHaveBeenCalled()
})
it("loads the changelog", async () => {
await renderMetricDetails()
await act(async () => fireEvent.click(screen.getByText(/Changelog/)))
expect(changelog_api.get_changelog).toHaveBeenCalledWith(5, { metric_uuid: "metric_uuid" })
})
it("deletes the metric", async () => {
await renderMetricDetails()
fireEvent.click(screen.getByText(/Delete metric/))
expect(fetch_server_api.fetch_server_api).toHaveBeenCalledWith("delete", "metric/metric_uuid", {})
})
it("measures the metric", async () => {
await renderMetricDetails()
fireEvent.click(screen.getByText(/Measure metric/))
expect(fetch_server_api.fetch_server_api).toHaveBeenCalledWith(
"post",
"metric/metric_uuid/attribute/measurement_requested",
expect.objectContaining({}), // Ignore the attribute value, it's new Date().toISOString()
)
})
it("fails to load measurements", async () => {
await renderMetricDetails(null, null, true)
fireEvent.click(screen.getByText(/Trend graph/))
expect(screen.queryAllByText(/Loading measurements failed/).length).toBe(1)
})
it("reloads the measurements after editing a measurement entity", async () => {
await renderMetricDetails()
expect(measurement_api.get_metric_measurements).toHaveBeenCalledTimes(1)
fireEvent.click(screen.getByText(/The source/))
fireEvent.click(screen.getByRole("button", { name: "Expand/collapse" }))
fireEvent.click(screen.getAllByText("Unconfirmed")[1])
await act(async () => fireEvent.click(screen.getByText("Confirm")))
expect(measurement_api.get_metric_measurements).toHaveBeenCalledTimes(2)
})