test: add SettingsPanel coverage

This commit is contained in:
Marc 2024-02-19 22:55:17 -05:00
parent 51c8b929b3
commit 954825e6a4
Signed by: marc
GPG key ID: 048E042F22B5DC79
2 changed files with 155 additions and 11 deletions

View file

@ -0,0 +1,134 @@
import { describe, it, expect, afterEach, vi } from "vitest";
import { screen, within } from "@testing-library/react";
import { renderComponent as renderComponentInner } from "@/testHelpers/renderUtils";
import * as LocalStorageHook from "@/hooks/useLocalStorage";
import SettingsPanel from "./SettingsPanel";
function renderComponent() {
return renderComponentInner(SettingsPanel);
}
describe("SettingsPanel", () => {
afterEach(() => {
vi.restoreAllMocks();
});
it("renders a text field to capture user-defined urls", async () => {
renderComponent();
const textField = await screen.getByLabelText(/feed urls/i, {
selector: "textarea",
});
expect(textField).toBeInTheDocument();
});
it("renders a submission button to save the settings state", async () => {
renderComponent();
const submitButton = await screen.getByLabelText(/update feeds/i, {
selector: "button",
});
expect(submitButton).toBeInTheDocument();
expect(submitButton.textContent).toEqual("Save");
});
it("pre-populates text field with saved user-defined urls", async () => {
const mockSettings = {
feedUrls: ["test-feed-url.com", "other-test-feed-url.ca"],
};
const mockLocalStorage = vi
.spyOn(LocalStorageHook, "default")
.mockReturnValue({
setValue: () => {},
// biome-ignore lint/suspicious/noExplicitAny: The function usually takes a generic.
getValue: (key: string): any => {
if (key === "settings") return mockSettings;
throw Error("Not implemented.");
},
});
renderComponent();
const textField = await screen.getByLabelText(/feed urls/i, {
selector: "textarea",
});
expect(textField.textContent).toEqual(mockSettings.feedUrls.join("\n"));
});
it("displays parsed urls from user input", async () => {
const mockSettings = {
feedUrls: ["test-feed-url.com", "other-test-feed-url.ca"],
};
const mockLocalStorage = vi
.spyOn(LocalStorageHook, "default")
.mockReturnValue({
setValue: () => {},
// biome-ignore lint/suspicious/noExplicitAny: The function usually takes a generic.
getValue: (key: string): any => {
if (key === "settings") return mockSettings;
throw Error("Not implemented.");
},
});
renderComponent();
// FIXME: Weak assertion / selection of component.
const parsedUrlCards = await screen.getByRole("list");
expect(parsedUrlCards.children.length).toEqual(
mockSettings.feedUrls.length,
);
expect(parsedUrlCards.children[0].textContent.trim()).toEqual(
mockSettings.feedUrls[0],
);
expect(parsedUrlCards.children[1].textContent.trim()).toEqual(
mockSettings.feedUrls[1],
);
});
it.each`
scenario | url | expectValid
${"valid"} | ${"https://www.my-feed.com"} | ${true}
${"invalid"} | ${"not-a-url"} | ${false}
`(
"displays validation status of saved user-defined urls ($scenario)",
async ({ url, expectValid }) => {
const mockSettings = {
feedUrls: [url],
};
const mockLocalStorage = vi
.spyOn(LocalStorageHook, "default")
.mockReturnValue({
setValue: () => {},
// biome-ignore lint/suspicious/noExplicitAny: The function usually takes a generic.
getValue: (key: string): any => {
if (key === "settings") return mockSettings;
throw Error("Not implemented.");
},
});
renderComponent();
const validatedUrl = await screen.getByText(url, {
selector: "[role=listitem]",
});
expect(
within(validatedUrl).getByTestId(
expectValid ? /CheckCircleOutlineIcon/ : /ErrorOutlineIcon/,
),
).toBeInTheDocument();
},
);
});

View file

@ -30,16 +30,25 @@ export default function SettingsPanel() {
const settings = getSettings();
const [feedUrlsForm, setFeedUrlsForm] = useState(settings.feedUrls);
const urlCards = feedUrlsForm.map((url) => (
<Card key={`url_${url}`} variant="outlined" style={urlCard}>
{isValidUrl(url) ? (
<CheckCircleOutlineIcon color="primary" />
) : (
<ErrorOutlineIcon color="error" />
)}{" "}
{url}
</Card>
));
const urlCards = feedUrlsForm.map((url) => {
const isValid = isValidUrl(url);
return (
<Card
key={`url_${url}`}
variant="outlined"
style={urlCard}
role="listitem"
>
{isValid ? (
<CheckCircleOutlineIcon color="primary" />
) : (
<ErrorOutlineIcon color="error" />
)}{" "}
{url}
</Card>
);
});
return (
<Box display="flex" flexDirection="column">
@ -55,12 +64,13 @@ export default function SettingsPanel() {
value={feedUrlsForm.join("\n")}
onChange={(v) => setFeedUrlsForm(v.target.value.split("\n"))}
/>
<Box display="flex" flexDirection="column">
<Box display="flex" flexDirection="column" role="list">
{urlCards}
</Box>
<Button
variant="contained"
color="primary"
aria-label="update feeds"
onClick={() => {
const validUrls = feedUrlsForm.filter(isValidUrl);
setSettings<string[]>("feedUrls", validUrls);