mirror of
https://seed.radicle.dev/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5.git
synced 2026-10-18 19:30:27 +08:00
Move input validation into lib
This commit is contained in:
@@ -1,12 +1,9 @@
|
||||
<script lang="ts">
|
||||
import type { Author } from "@bindings/cob/Author";
|
||||
|
||||
import { parseAssignee } from "@app/lib/inputValidation";
|
||||
import { invoke } from "@app/lib/invoke";
|
||||
import {
|
||||
authorForNodeId,
|
||||
parseNodeId,
|
||||
publicKeyFromDid,
|
||||
} from "@app/lib/utils";
|
||||
import { authorForNodeId, publicKeyFromDid } from "@app/lib/utils";
|
||||
|
||||
import Button from "@app/components/Button.svelte";
|
||||
import Icon from "@app/components/Icon.svelte";
|
||||
@@ -32,9 +29,9 @@
|
||||
let updatedAssignees: Author[] = $state([]);
|
||||
let showInput: boolean = $state(false);
|
||||
let inputValue = $state("");
|
||||
let validationMessage: string | undefined = $state(undefined);
|
||||
let valid: boolean = $state(false);
|
||||
let assignee: string | undefined = undefined;
|
||||
const parsed = $derived(parseAssignee(inputValue, updatedAssignees));
|
||||
const validationMessage = $derived(parsed.error);
|
||||
const valid = $derived(parsed.error === undefined);
|
||||
|
||||
let removeToggles: Record<string, boolean> = $state({});
|
||||
|
||||
@@ -45,35 +42,12 @@
|
||||
updatedAssignees = assignees;
|
||||
|
||||
showInput = false;
|
||||
validationMessage = undefined;
|
||||
valid = true;
|
||||
removeToggles = {};
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (inputValue === "") {
|
||||
validationMessage = "";
|
||||
valid = true;
|
||||
} else {
|
||||
const parsedNodeId = parseNodeId(inputValue);
|
||||
if (parsedNodeId) {
|
||||
assignee = `${parsedNodeId.prefix}${parsedNodeId.pubkey}`;
|
||||
if (updatedAssignees.find(({ did }) => did === assignee)) {
|
||||
validationMessage = "This assignee is already added";
|
||||
valid = false;
|
||||
} else {
|
||||
validationMessage = undefined;
|
||||
valid = true;
|
||||
}
|
||||
} else {
|
||||
validationMessage = "This is not a valid DID";
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
async function addAssignee() {
|
||||
if (valid && assignee) {
|
||||
const assignee = parsed.did;
|
||||
if (assignee) {
|
||||
const alias = await invoke<string | null>("alias", {
|
||||
nid: publicKeyFromDid(assignee),
|
||||
});
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { labelError } from "@app/lib/inputValidation";
|
||||
|
||||
import Button from "@app/components/Button.svelte";
|
||||
import Icon from "@app/components/Icon.svelte";
|
||||
import Label from "@app/components/Label.svelte";
|
||||
@@ -23,10 +25,9 @@
|
||||
let updatedLabels: string[] = $state([]);
|
||||
let showInput: boolean = $state(false);
|
||||
let inputValue = $state("");
|
||||
let validationMessage: string | undefined = $state();
|
||||
let valid: boolean = $state(false);
|
||||
|
||||
const sanitizedValue = $derived(inputValue.trim());
|
||||
const validationMessage = $derived(labelError(inputValue, updatedLabels));
|
||||
const valid = $derived(validationMessage === undefined);
|
||||
|
||||
let removeToggles: Record<string, boolean> = $state({});
|
||||
|
||||
@@ -37,28 +38,9 @@
|
||||
updatedLabels = labels;
|
||||
|
||||
showInput = false;
|
||||
validationMessage = undefined;
|
||||
valid = true;
|
||||
removeToggles = {};
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (inputValue !== "") {
|
||||
if (sanitizedValue.length > 0) {
|
||||
if (updatedLabels.includes(sanitizedValue)) {
|
||||
valid = false;
|
||||
validationMessage = "This label is already assigned";
|
||||
} else {
|
||||
valid = true;
|
||||
validationMessage = undefined;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
valid = true;
|
||||
validationMessage = "";
|
||||
}
|
||||
});
|
||||
|
||||
function addLabel() {
|
||||
if (valid && sanitizedValue) {
|
||||
updatedLabels = [...updatedLabels, sanitizedValue].sort();
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { parseNodeId } from "@app/lib/utils";
|
||||
|
||||
export type AliasError =
|
||||
| "AliasError.EmptyAlias"
|
||||
| "AliasError.TooLongAlias"
|
||||
| "AliasError.InvalidAlias";
|
||||
|
||||
export function aliasErrors(alias: string): AliasError[] {
|
||||
const errors: AliasError[] = [];
|
||||
if (alias.length === 0) errors.push("AliasError.EmptyAlias");
|
||||
if (alias.length > 32) errors.push("AliasError.TooLongAlias");
|
||||
if (alias.includes(" ")) errors.push("AliasError.InvalidAlias");
|
||||
return errors;
|
||||
}
|
||||
|
||||
export function labelError(
|
||||
label: string,
|
||||
labels: string[],
|
||||
): string | undefined {
|
||||
if (labels.includes(label.trim())) {
|
||||
return "This label is already assigned";
|
||||
}
|
||||
}
|
||||
|
||||
export function parseAssignee(
|
||||
input: string,
|
||||
assignees: { did: string }[],
|
||||
): { did?: string; error?: string } {
|
||||
if (input === "") return {};
|
||||
const nodeId = parseNodeId(input);
|
||||
if (!nodeId) return { error: "This is not a valid DID" };
|
||||
const did = `${nodeId.prefix}${nodeId.pubkey}`;
|
||||
if (assignees.some(assignee => assignee.did === did)) {
|
||||
return { error: "This assignee is already added" };
|
||||
}
|
||||
return { did };
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
import debounce from "lodash/debounce";
|
||||
|
||||
import { aliasErrors } from "@app/lib/inputValidation";
|
||||
import { invoke } from "@app/lib/invoke";
|
||||
import * as router from "@app/lib/router";
|
||||
import { createEventEmittersOnce } from "@app/lib/startup.svelte";
|
||||
@@ -28,23 +29,15 @@
|
||||
}, 400);
|
||||
|
||||
function validateInput(field: "alias" | "passphrase") {
|
||||
if (field === "alias" && alias.length === 0) {
|
||||
errors.alias.push({ code: "AliasError.EmptyAlias" });
|
||||
}
|
||||
if (field === "alias" && alias.length > 32) {
|
||||
errors.alias.push({ code: "AliasError.TooLongAlias" });
|
||||
}
|
||||
if (field === "alias" && alias.includes(" ")) {
|
||||
errors.alias.push({ code: "AliasError.InvalidAlias" });
|
||||
if (field === "alias") {
|
||||
errors.alias = aliasErrors(alias).map(code => ({ code }));
|
||||
}
|
||||
if (field === "passphrase" && passphrase.length === 0) {
|
||||
errors.passphrase.push({ code: "PassphraseError.InvalidPassphrase" });
|
||||
}
|
||||
}
|
||||
|
||||
const validAlias = $derived(
|
||||
alias.length > 0 && alias.length <= 32 && !alias.includes(" "),
|
||||
);
|
||||
const validAlias = $derived(aliasErrors(alias).length === 0);
|
||||
const validPassphrase = $derived(
|
||||
passphrase.length > 0 && passphrase === passphraseRepeat,
|
||||
);
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
|
||||
import {
|
||||
aliasErrors,
|
||||
labelError,
|
||||
parseAssignee,
|
||||
} from "@app/lib/inputValidation";
|
||||
|
||||
const nid = "z6MkqGC3nWZhYieEVTVDKW5v588CiGfsDSmRVG9ZwwWTvLSK";
|
||||
|
||||
test.each([
|
||||
["alice", []],
|
||||
["", ["AliasError.EmptyAlias"]],
|
||||
["a".repeat(32), []],
|
||||
["a".repeat(33), ["AliasError.TooLongAlias"]],
|
||||
["a b", ["AliasError.InvalidAlias"]],
|
||||
[
|
||||
`${"a ".repeat(17)}`,
|
||||
["AliasError.TooLongAlias", "AliasError.InvalidAlias"],
|
||||
],
|
||||
])("aliasErrors(%j) is %j", (alias, expected) => {
|
||||
expect(aliasErrors(alias)).toEqual(expected);
|
||||
});
|
||||
|
||||
test("labelError flags a label that is already assigned", () => {
|
||||
expect(labelError(" bug ", ["bug"])).toBe("This label is already assigned");
|
||||
expect(labelError("feature", ["bug"])).toBeUndefined();
|
||||
expect(labelError(" ", ["bug"])).toBeUndefined();
|
||||
});
|
||||
|
||||
describe("parseAssignee", () => {
|
||||
test("accepts a NID or a DID", () => {
|
||||
expect(parseAssignee(nid, [])).toEqual({ did: `did:key:${nid}` });
|
||||
expect(parseAssignee(`did:key:${nid}`, [])).toEqual({
|
||||
did: `did:key:${nid}`,
|
||||
});
|
||||
});
|
||||
|
||||
test("rejects an assignee that is already added", () => {
|
||||
expect(parseAssignee(nid, [{ did: `did:key:${nid}` }])).toEqual({
|
||||
error: "This assignee is already added",
|
||||
});
|
||||
});
|
||||
|
||||
test("rejects anything else, but not an empty input", () => {
|
||||
expect(parseAssignee("alice", [])).toEqual({
|
||||
error: "This is not a valid DID",
|
||||
});
|
||||
expect(parseAssignee("", [])).toEqual({});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user