Skip to content

Commit

Permalink
Merge 4a93376 into 94ca405
Browse files Browse the repository at this point in the history
  • Loading branch information
macfarlandian committed Jan 27, 2021
2 parents 94ca405 + 4a93376 commit 8345e29
Show file tree
Hide file tree
Showing 22 changed files with 1,057 additions and 55 deletions.
4 changes: 4 additions & 0 deletions spotlight-client/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@
{
"name": "react-spring",
"message": "For IE 11 support, please import from react-spring/web.cjs"
},
{
"name": "react-spring/renderprops",
"message": "For IE 11 support, please import from react-spring/renderprops.cjs"
}
]
}
Expand Down
4 changes: 3 additions & 1 deletion spotlight-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@
"d3-interpolate": "^2.0.1",
"d3-scale": "^3.2.3",
"date-fns": "^2.16.1",
"downshift": "^6.1.0",
"html-react-parser": "^1.1.1",
"intersection-observer": "^0.12.0",
"jest-fetch-mock": "^3.0.3",
"lodash.isempty": "^4.4.0",
"mobx": "^6.0.4",
"mobx-react-lite": "^3.0.1",
Expand Down Expand Up @@ -78,7 +78,9 @@
"@typescript-eslint/parser": "^4.4.0",
"env-cmd": "^10.1.0",
"eslint-import-resolver-typescript": "^2.3.0",
"jest-date-mock": "^1.0.8",
"jest-environment-jsdom-sixteen": "^1.0.3",
"jest-fetch-mock": "^3.0.3",
"lint-staged": ">=10"
},
"browserslist": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Recidiviz - a data platform for criminal justice reform
// Copyright (C) 2021 Recidiviz, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// =============================================================================

import { render, screen, fireEvent } from "@testing-library/react";
import React from "react";
import createMetricMapping from "../contentModels/createMetricMapping";
import type HistoricalPopulationBreakdownMetric from "../contentModels/HistoricalPopulationBreakdownMetric";
import contentFixture from "../contentModels/__fixtures__/tenant_content_exhaustive";
import { reactImmediately } from "../testUtils";
import DemographicFilterSelect from "./DemographicFilterSelect";

const testTenantId = "US_ND";
const testMetricId = "ProbationPopulationHistorical";
const testMetadataMapping = {
[testMetricId]: contentFixture.metrics[testMetricId],
};

function getTestMetric() {
return createMetricMapping({
metadataMapping: testMetadataMapping,
tenantId: testTenantId,
}).get(testMetricId) as HistoricalPopulationBreakdownMetric;
}

test("has expected options", () => {
const metric = getTestMetric();
render(<DemographicFilterSelect metric={metric} />);

const menuButton = screen.getByRole("button", {
name: "View Total",
});
fireEvent.click(menuButton);

const options = screen.getAllByRole("option");

expect(options.length).toBe(4);

expect(options[0]).toHaveTextContent("Total");
expect(options[1]).toHaveTextContent("Race");
expect(options[2]).toHaveTextContent("Gender");
expect(options[3]).toHaveTextContent("Age");
});

test("changes demographic filter", () => {
const metric = getTestMetric();
render(<DemographicFilterSelect metric={metric} />);

const menuButton = screen.getByRole("button", {
name: "View Total",
});
fireEvent.click(menuButton);

const raceOption = screen.getByRole("option", { name: "Race" });
fireEvent.click(raceOption);

reactImmediately(() => {
expect(metric.demographicView).toBe("race");
expect(menuButton).toHaveTextContent("Race");
});

fireEvent.click(menuButton);
const genderOption = screen.getByRole("option", { name: "Gender" });
fireEvent.click(genderOption);
reactImmediately(() => {
expect(metric.demographicView).toBe("gender");
expect(menuButton).toHaveTextContent("Gender");
});

fireEvent.click(menuButton);

const ageOption = screen.getByRole("option", { name: "Age" });
fireEvent.click(ageOption);
reactImmediately(() => {
expect(metric.demographicView).toBe("age");
expect(menuButton).toHaveTextContent("Age");
});

expect.hasAssertions();
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Recidiviz - a data platform for criminal justice reform
// Copyright (C) 2021 Recidiviz, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// =============================================================================

import { action } from "mobx";
import { observer } from "mobx-react-lite";
import React from "react";
import HistoricalPopulationBreakdownMetric from "../contentModels/HistoricalPopulationBreakdownMetric";
import { DemographicView, isDemographicView } from "../metricsApi";
import { Dropdown } from "../UiLibrary";

type DemographicFilterOption = { id: DemographicView; label: string };

type DemographicFilterSelectProps = {
metric: HistoricalPopulationBreakdownMetric;
};

const DemographicFilterSelect: React.FC<DemographicFilterSelectProps> = ({
metric,
}) => {
const options: DemographicFilterOption[] = [
{ id: "total", label: "Total" },
{ id: "race", label: "Race" },
{ id: "gender", label: "Gender" },
{ id: "age", label: "Age" },
];

const onChange = action("change demographic filter", (newFilter: string) => {
if (isDemographicView(newFilter)) {
// eslint-disable-next-line no-param-reassign
metric.demographicView = newFilter;
}
});

return (
<Dropdown
label="View"
onChange={onChange}
options={options}
selectedId={metric.demographicView}
/>
);
};

export default observer(DemographicFilterSelect);
18 changes: 18 additions & 0 deletions spotlight-client/src/DemographicFilterSelect/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Recidiviz - a data platform for criminal justice reform
// Copyright (C) 2021 Recidiviz, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// =============================================================================

export { default } from "./DemographicFilterSelect";
151 changes: 151 additions & 0 deletions spotlight-client/src/UiLibrary/Dropdown/Dropdown.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
// Recidiviz - a data platform for criminal justice reform
// Copyright (C) 2020 Recidiviz, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// =============================================================================

import { render, fireEvent, screen, waitFor } from "@testing-library/react";
import React from "react";
import Dropdown from "./Dropdown";

const testLabel = "Test Label";
const mockOnChange = jest.fn();
const testOptions = [
{ id: "1", label: "option one" },
{ id: "2", label: "option two" },
{ id: "3", label: "option three" },
];

beforeEach(() => {
jest.resetAllMocks();
});

test("select from menu", async () => {
render(
<Dropdown
label={testLabel}
options={testOptions}
onChange={mockOnChange}
selectedId="1"
/>
);

const menuButton = screen.getByRole("button", {
name: `${testLabel} ${testOptions[0].label}`,
});

screen
.queryAllByRole("option")
.forEach((option) => expect(option).not.toBeInTheDocument());

fireEvent.click(menuButton);

testOptions.forEach((opt) => {
expect(screen.getByRole("option", { name: opt.label })).toBeVisible();
});

const newOption = screen.getByRole("option", { name: testOptions[2].label });
fireEvent.click(newOption);

expect(mockOnChange.mock.calls[0][0]).toBe(testOptions[2].id);

// menu closes after selection is made; slight delay due to animation
await waitFor(() =>
screen.queryAllByRole("option").forEach((opt) => {
expect(opt).not.toBeInTheDocument();
})
);
});

test("selection prop updates menu", () => {
const { rerender } = render(
<Dropdown
label={testLabel}
options={testOptions}
onChange={mockOnChange}
selectedId="1"
/>
);

const menuButton = screen.getByRole("button", {
name: `${testLabel} ${testOptions[0].label}`,
});

rerender(
<Dropdown
label={testLabel}
options={testOptions}
onChange={mockOnChange}
selectedId="2"
/>
);

expect(menuButton).toHaveTextContent(testOptions[1].label);
});

test("can be disabled", () => {
render(
<Dropdown
label={testLabel}
options={testOptions}
onChange={mockOnChange}
selectedId="1"
disabled
/>
);
const menuButton = screen.getByRole("button", {
name: `${testLabel} ${testOptions[0].label}`,
});
expect(menuButton).toBeDisabled();

fireEvent.click(menuButton);
screen
.queryAllByRole("option")
.forEach((option) => expect(option).not.toBeInTheDocument());
});

test("options can be hidden", () => {
const hiddenOption = { id: "4", label: "Hidden option", hidden: true };
const testOptionsHidden = [...testOptions, hiddenOption];
const { rerender } = render(
<Dropdown
label={testLabel}
options={testOptionsHidden}
onChange={mockOnChange}
selectedId="1"
/>
);

const menuButton = screen.getByRole("button", {
name: `${testLabel} ${testOptions[0].label}`,
});

fireEvent.click(menuButton);

expect(
screen.queryByRole("option", { name: hiddenOption.label })
).not.toBeInTheDocument();

// can still be set by controlling component
rerender(
<Dropdown
label={testLabel}
options={testOptionsHidden}
onChange={mockOnChange}
selectedId={hiddenOption.id}
/>
);

expect(menuButton).toHaveTextContent(hiddenOption.label);
});
Loading

0 comments on commit 8345e29

Please sign in to comment.