milestone 21: abp list exceptions and a regex rule kind
This commit is contained in:
@@ -34,6 +34,7 @@ const RESPONSES: Record<string, unknown> = {
|
||||
// The API orders groups by name, so the id-1 default is not always first.
|
||||
let groups: { id: number; name: string; safe_search: boolean }[];
|
||||
let deleted: string[];
|
||||
let posted: { pattern: string; kind: string }[];
|
||||
|
||||
function deleteCalls(): string[] {
|
||||
return deleted;
|
||||
@@ -45,6 +46,7 @@ beforeEach(() => {
|
||||
{ id: 2, name: "Kids", safe_search: true },
|
||||
];
|
||||
deleted = [];
|
||||
posted = [];
|
||||
vi.stubGlobal(
|
||||
"fetch",
|
||||
vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
|
||||
@@ -54,6 +56,7 @@ beforeEach(() => {
|
||||
return new Response(null, { status: 204 });
|
||||
}
|
||||
if (url === "/api/rules" && init?.method === "POST") {
|
||||
posted.push(JSON.parse(String(init.body)) as { pattern: string; kind: string });
|
||||
return new Response(JSON.stringify({ error: "rate limited" }), {
|
||||
status: 429,
|
||||
headers: { "content-type": "application/json", "Retry-After": "5" },
|
||||
@@ -116,11 +119,61 @@ test("renders the rule table and the create form with contract enums", async ()
|
||||
expect(table.getByText("Kids")).toBeTruthy();
|
||||
expect(screen.getAllByRole("button", { name: "Delete" })).toHaveLength(2);
|
||||
|
||||
expect(await optionsOf("Kind")).toEqual(["exact", "wildcard"]);
|
||||
expect(await optionsOf("Kind")).toEqual(["exact", "wildcard", "regex"]);
|
||||
expect(await optionsOf("Action")).toEqual(["allow", "block"]);
|
||||
expect(await optionsOf("Group")).toEqual(["Default", "Kids"]);
|
||||
});
|
||||
|
||||
test("the kind selector can select the regex option, not only list it", async () => {
|
||||
renderRulesRoute();
|
||||
await screen.findByRole("heading", { name: "Rules" });
|
||||
|
||||
fireEvent.click(trigger("Kind"));
|
||||
const options = await screen.findAllByRole("option");
|
||||
const regex = options.find((option) => option.textContent === "regex");
|
||||
expect(regex).toBeTruthy();
|
||||
fireEvent.click(regex!);
|
||||
await waitFor(() => expect(screen.queryByRole("listbox")).toBeNull());
|
||||
|
||||
expect(trigger("Kind").textContent).toContain("regex");
|
||||
});
|
||||
|
||||
async function selectKind(label: string): Promise<void> {
|
||||
fireEvent.click(trigger("Kind"));
|
||||
const options = await screen.findAllByRole("option");
|
||||
fireEvent.click(options.find((option) => option.textContent === label)!);
|
||||
await waitFor(() => expect(screen.queryByRole("listbox")).toBeNull());
|
||||
}
|
||||
|
||||
// A regex is stored and matched byte for byte, so whitespace inside it is data,
|
||||
// not slop the UI may drop. Exact and wildcard are normalized server-side.
|
||||
test("a regex pattern is posted untrimmed, an exact pattern is trimmed", async () => {
|
||||
renderRulesRoute();
|
||||
await screen.findByRole("heading", { name: "Rules" });
|
||||
|
||||
await selectKind("regex");
|
||||
fireEvent.change(screen.getByLabelText("Pattern"), { target: { value: " foo|bar " } });
|
||||
fireEvent.click(screen.getByRole("button", { name: "Create rule" }));
|
||||
await waitFor(() => expect(posted).toHaveLength(1));
|
||||
expect(posted[0]).toMatchObject({ pattern: " foo|bar ", kind: "regex" });
|
||||
|
||||
await selectKind("exact");
|
||||
fireEvent.change(screen.getByLabelText("Pattern"), { target: { value: " ads.example.net " } });
|
||||
fireEvent.click(screen.getByRole("button", { name: "Create rule" }));
|
||||
await waitFor(() => expect(posted).toHaveLength(2));
|
||||
expect(posted[1]).toMatchObject({ pattern: "ads.example.net", kind: "exact" });
|
||||
});
|
||||
|
||||
test("the pattern field opts out of mobile autocapitalize and autocorrect", async () => {
|
||||
renderRulesRoute();
|
||||
await screen.findByRole("heading", { name: "Rules" });
|
||||
|
||||
const input = screen.getByLabelText("Pattern");
|
||||
expect(input.getAttribute("autocapitalize")).toBe("none");
|
||||
expect(input.getAttribute("autocorrect")).toBe("off");
|
||||
expect(input.getAttribute("spellcheck")).toBe("false");
|
||||
});
|
||||
|
||||
test("rule create shows a countdown when rate limited with Retry-After", async () => {
|
||||
renderRulesRoute();
|
||||
await screen.findByRole("heading", { name: "Rules" });
|
||||
|
||||
Reference in New Issue
Block a user