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.
91 lines
3.2 KiB
TypeScript
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." });
|
|
});
|