Files
nxdns/admin/src/features/clients/prefixEditor.test.ts
T
mokhtar 59d6be98f8 admin: associate inline field errors with their inputs
network assignment rows, activity filters, and the settings password pair now mark the offending input with aria-invalid and point it at the error text with aria-describedby. the prefix validator returns the row and field it is about, and any row mutation clears a message that named a position. settings numeric fields carry aria-invalid on an unparseable value.
2026-08-29 12:08:19 +02:00

91 lines
3.2 KiB
TypeScript

import type { ClientPrefix } from "@/lib/types";
import {
firstProblem,
initPrefixEditor,
isDirty,
prefixEditorReducer,
toInputs,
type PrefixEditorState,
} from "./prefixEditor";
const server: ClientPrefix[] = [
{ id: 1, prefix: "192.168.1.0/24", group_id: 1, group: "default", priority: 100 },
{ id: 2, prefix: "10.0.0.0/8", group_id: 2, group: "kids", priority: 50 },
];
test("init mirrors the server rows into baseline and rows", () => {
const state = initPrefixEditor(server);
expect(state.rows).toEqual([
{ prefix: "192.168.1.0/24", group_id: 1, priority: "100" },
{ prefix: "10.0.0.0/8", group_id: 2, priority: "50" },
]);
expect(state.baseline).toEqual(state.rows);
expect(isDirty(state)).toBe(false);
});
test("add appends an empty row with the given group and marks dirty", () => {
const state = prefixEditorReducer(initPrefixEditor(server), { type: "add", groupId: 1 });
expect(state.rows).toHaveLength(3);
expect(state.rows[2]).toEqual({ prefix: "", group_id: 1, priority: "" });
expect(isDirty(state)).toBe(true);
});
test("remove drops the row at the index", () => {
const state = prefixEditorReducer(initPrefixEditor(server), { type: "remove", index: 0 });
expect(state.rows).toEqual([{ prefix: "10.0.0.0/8", group_id: 2, priority: "50" }]);
expect(isDirty(state)).toBe(true);
});
test("edit patches a single row", () => {
const state = prefixEditorReducer(initPrefixEditor(server), {
type: "edit",
index: 1,
patch: { group_id: 1, priority: "10" },
});
expect(state.rows[1]).toEqual({ prefix: "10.0.0.0/8", group_id: 1, priority: "10" });
expect(state.rows[0]).toEqual(state.baseline[0]);
expect(isDirty(state)).toBe(true);
});
test("editing a field back to its baseline value is clean again", () => {
let state: PrefixEditorState = initPrefixEditor(server);
state = prefixEditorReducer(state, { type: "edit", index: 0, patch: { priority: "7" } });
expect(isDirty(state)).toBe(true);
state = prefixEditorReducer(state, { type: "edit", index: 0, patch: { priority: "100" } });
expect(isDirty(state)).toBe(false);
});
test("reset adopts new server rows and clears dirtiness", () => {
let state = prefixEditorReducer(initPrefixEditor(server), { type: "add", groupId: 1 });
state = prefixEditorReducer(state, { type: "reset", prefixes: server });
expect(isDirty(state)).toBe(false);
expect(state.rows).toHaveLength(2);
});
test("toInputs trims prefixes, parses priorities and omits empty ones", () => {
expect(
toInputs([
{ prefix: " 192.168.1.0/24 ", group_id: 1, priority: "25" },
{ prefix: "10.0.0.0/8", group_id: 2, priority: "" },
]),
).toEqual([
{ prefix: "192.168.1.0/24", group_id: 1, priority: 25 },
{ prefix: "10.0.0.0/8", group_id: 2 },
]);
});
test("firstProblem flags empty prefixes and non-integer priorities", () => {
expect(firstProblem([{ prefix: "10.0.0.0/8", group_id: 1, priority: "" }])).toBeNull();
expect(firstProblem([{ prefix: " ", group_id: 1, priority: "" }])).toEqual({
index: 0,
field: "prefix",
message: "Row 1: prefix is required.",
});
expect(
firstProblem([
{ prefix: "10.0.0.0/8", group_id: 1, priority: "100" },
{ prefix: "10.1.0.0/16", group_id: 1, priority: "abc" },
]),
).toEqual({ index: 1, field: "priority", message: "Row 2: priority must be a whole number." });
});