import * as React from "react";
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { PatronBlockingRulesHelpModal } from "../../../src/components/PatronBlockingRulesHelpModal";
const SAMPLE_FIELDS = {
fines: "2.50",
patron_identifier: "12345",
patron_name: "John Doe",
};
const baseProps = {
show: true,
onHide: jest.fn(),
availableFields: null,
fieldsLoading: false,
fieldsError: null,
};
describe("PatronBlockingRulesHelpModal", () => {
beforeEach(() => {
baseProps.onHide = jest.fn();
});
it("renders the modal title when show is true", () => {
render();
expect(screen.getByText(/Patron Blocking Rules — Help/i)).toBeTruthy();
});
it("does not render when show is false", () => {
render();
expect(
screen.queryByText(/Patron Blocking Rules — Help/i)
).toBeNull();
});
it("shows a loading indicator when fieldsLoading is true", () => {
render(
);
expect(screen.getByText(/Loading available fields/i)).toBeTruthy();
});
it("does not show the fields table while loading", () => {
render(
);
// Patron field names should not be visible while loading.
expect(screen.queryByText("fines")).toBeNull();
expect(screen.queryByText("patron_identifier")).toBeNull();
});
it("shows available fields in a table when loaded successfully", () => {
render(
);
// Verify the fields table content is present.
expect(screen.getByText("fines")).toBeTruthy();
expect(screen.getByText("2.50")).toBeTruthy();
expect(screen.getByText("patron_identifier")).toBeTruthy();
expect(screen.getByText("12345")).toBeTruthy();
expect(screen.getByText("patron_name")).toBeTruthy();
expect(screen.getByText("John Doe")).toBeTruthy();
});
it("shows a warning message when fieldsError is set", () => {
render(
);
expect(
screen.getByText(/Save the service before template variables can be fetched/i)
).toBeTruthy();
expect(screen.queryByText("fines")).toBeNull();
});
it("shows a muted fallback when there are no fields and no error", () => {
render(
);
expect(
screen.getByText(/No field data available/i)
).toBeTruthy();
});
it("renders null values as italic 'null'", () => {
render(
);
expect(screen.getByText("null")).toBeTruthy();
});
it("renders the Available Functions section with content", () => {
render();
expect(screen.getByText(/Available Functions/i)).toBeTruthy();
});
it("renders the Syntax Reference section", () => {
render();
expect(screen.getByText(/Syntax Reference/i)).toBeTruthy();
// simpleeval appears in both the functions doc and the syntax section.
expect(screen.getAllByText(/simpleeval/i).length).toBeGreaterThan(0);
});
it("calls onHide when the header × button is clicked", async () => {
const user = userEvent.setup();
const onHide = jest.fn();
render();
// The header × button has class "close"; the footer button has content "Close".
const closeButtons = screen.getAllByRole("button", { name: /close/i });
await user.click(closeButtons[0]);
expect(onHide).toHaveBeenCalledTimes(1);
});
it("calls onHide when the footer Close button is clicked", async () => {
const user = userEvent.setup();
const onHide = jest.fn();
render();
// Footer Close is the last close-named button; header × is first.
const closeButtons = screen.getAllByRole("button", { name: /close/i });
await user.click(closeButtons[closeButtons.length - 1]);
expect(onHide).toHaveBeenCalledTimes(1);
});
});