1
0
forked from baron/baron-sso

test: raise frontend coverage baselines

This commit is contained in:
2026-05-29 14:31:10 +09:00
parent 592c1d1741
commit 3e31fdfa0c
50 changed files with 3482 additions and 214 deletions

View File

@@ -35,7 +35,6 @@ import {
CardHeader,
CardTitle,
} from "../../components/ui/card";
import { Checkbox } from "../../components/ui/checkbox";
import {
Dialog,
DialogContent,
@@ -95,8 +94,8 @@ import { resolvePersonalTenant } from "./utils/personalTenant";
type UserFormValues = Omit<UserUpdateRequest, "metadata"> & {
email: string;
metadata: Record<string, Record<string, string | number | boolean>> & {
sub_email?: string[];
metadata: Record<string, unknown> & {
sub_email?: string | string[];
};
};
type UserCategory = "hanmac" | "external" | "personal";
@@ -109,6 +108,44 @@ type AppointmentDraft = UserAppointment & {
const PASSWORD_RESET_MIN_LENGTH = 12;
function isMetadataRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function cleanMetadataValue(value: unknown): unknown {
if (Array.isArray(value)) {
return value
.filter((item): item is string => typeof item === "string")
.map((item) => item.trim())
.filter(Boolean);
}
if (isMetadataRecord(value)) {
return Object.fromEntries(
Object.entries(value).filter(
([_, fieldValue]) =>
fieldValue !== undefined && fieldValue !== null && fieldValue !== "",
),
);
}
return value;
}
function normalizeSubEmails(value: unknown): string[] {
if (Array.isArray(value)) {
return value
.filter((item): item is string => typeof item === "string")
.map((item) => item.trim())
.filter((item) => item.includes("@"));
}
if (typeof value === "string" && value.trim() !== "") {
return value
.split(/[;,\n\r\t]/)
.map((email) => email.trim())
.filter((email) => email.includes("@"));
}
return [];
}
function createDraftId() {
return globalThis.crypto?.randomUUID?.() ?? `appointment-${Date.now()}`;
}
@@ -322,8 +359,8 @@ function UserDetailPage() {
const userId = params.id ?? "";
const navigate = useNavigate();
const queryClient = useQueryClient();
const [error, setError] = React.useState<string | null>(null);
const [successMsg, setSuccessMsg] = React.useState<string | null>(null);
const [_error, _setError] = React.useState<string | null>(null);
const [_successMsg, _setSuccessMsg] = React.useState<string | null>(null);
const [isPasswordResetOpen, setIsPasswordResetOpen] = React.useState(false);
const [generatedPassword, setGeneratedPassword] = React.useState<
string | null
@@ -419,7 +456,7 @@ function UserDetailPage() {
if (e.key === "Enter" || e.key === "," || e.key === " ") {
e.preventDefault();
const value = newSubEmail.trim().replace(/,/g, "");
if (value && value.includes("@") && !currentSubEmails.includes(value)) {
if (value?.includes("@") && !currentSubEmails.includes(value)) {
setValue("metadata.sub_email", [...currentSubEmails, value], {
shouldDirty: true,
});
@@ -595,7 +632,7 @@ function UserDetailPage() {
);
};
const setPrimaryAppointment = (targetIndex: number) => {
const _setPrimaryAppointment = (targetIndex: number) => {
setAdditionalAppointments((current) =>
current.map((appointment, index) => ({
...appointment,
@@ -774,15 +811,17 @@ function UserDetailPage() {
});
const onSubmit = async (data: UserFormValues) => {
// Filter out undefined/null/empty strings from metadata
const cleanMetadata = Object.fromEntries(
Object.entries(data.metadata).map(([tenantId, fields]) => {
const cleanFields = Object.fromEntries(
Object.entries(fields).filter(
([_, v]) => v !== undefined && v !== null && v !== "",
),
);
return [tenantId, cleanFields];
Object.entries(data.metadata ?? {}).flatMap(([key, value]) => {
const cleanedValue = cleanMetadataValue(value);
if (
cleanedValue === undefined ||
cleanedValue === null ||
cleanedValue === ""
) {
return [];
}
return [[key, cleanedValue]];
}),
);
@@ -792,19 +831,11 @@ function UserDetailPage() {
sub_email: rawSubEmail,
...safeMetadata
} = cleanMetadata;
// Parse sub_email
let sub_email: string[] = [];
if (typeof rawSubEmail === "string" && rawSubEmail.trim() !== "") {
sub_email = rawSubEmail
.split(/[;,\n\r\t]/)
.map((e) => e.trim())
.filter((e) => e.includes("@"));
}
const subEmail = normalizeSubEmails(rawSubEmail);
const metadata: Record<string, unknown> = {
...safeMetadata,
...(sub_email.length > 0 ? { sub_email } : { sub_email: [] }),
...(subEmail.length > 0 ? { sub_email: subEmail } : { sub_email: [] }),
};
const payload: UserUpdateRequest = {
@@ -813,7 +844,7 @@ function UserDetailPage() {
};
// email cannot be updated directly via this API in current backend implementation,
// so we delete it from payload if it spread
// @ts-ignore
// @ts-expect-error
delete payload.email;
payload.role = undefined;
@@ -989,8 +1020,7 @@ function UserDetailPage() {
<Mail size={14} className="text-primary/70" />
{user.email}
</div>
{user.metadata?.sub_email &&
Array.isArray(user.metadata.sub_email) &&
{Array.isArray(user.metadata?.sub_email) &&
user.metadata.sub_email.length > 0 && (
<div className="flex items-center gap-1.5 bg-background px-2.5 py-1 rounded-full border">
<Mail size={14} className="text-primary/40" />
@@ -1167,8 +1197,7 @@ function UserDetailPage() {
onClick={() => {
const value = newSubEmail.trim().replace(/,/g, "");
if (
value &&
value.includes("@") &&
value?.includes("@") &&
!currentSubEmails.includes(value)
) {
setValue(