test(forms): extract pure decision logic and add exhaustive coverage
The forms security/correctness logic was buried in DB-coupled server actions and the builder component, so it had no real tests (the suite is pure-logic only; nothing exercises Supabase). Extract the pure parts behaviour-unchanged into lib/forms-logic.ts and test them for real: - csvCell (results-export escaping), parseClientIp (e-sign IP capture filtering), shouldBumpFormVersion (waiver non-blocking re-sign), evaluateRequiredFormStep (onboarding required/auto-satisfy/fail-open), slugify + deriveFieldKeys (builder key derivation + dedupe) - forms.ts / onboarding.ts / form-builder.tsx now consume these; the local copies are removed (no behaviour change — verified by the full suite staying green) - __tests__/forms.test.ts expanded 16 -> 62: every validateAnswers type/branch (type coercion, length caps, required, unknown-key stripping, first-error ordering), parseFormSchema, every new Zod schema incl. formSlug boundaries, and all extracted helpers 333/333 tests; build verified (Compiled successfully, exit 0). No deploy.
This commit is contained in:
@@ -20,6 +20,7 @@ import {
|
||||
} from '@/lib/actions'
|
||||
import { formFieldTypes } from '@/lib/validations'
|
||||
import type { FormField } from '@/lib/forms-schema'
|
||||
import { slugify, deriveFieldKeys } from '@/lib/forms-logic'
|
||||
import { FormRenderer } from './form-renderer'
|
||||
|
||||
type Kind = 'form' | 'waiver'
|
||||
@@ -49,27 +50,7 @@ const TYPE_LABELS: Record<(typeof formFieldTypes)[number], string> = {
|
||||
radio: 'Choice',
|
||||
}
|
||||
|
||||
function slugify(s: string, sep: '-' | '_') {
|
||||
const cleaned = s
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, sep)
|
||||
.replace(new RegExp(`^\\${sep}+|\\${sep}+$`, 'g'), '')
|
||||
return cleaned
|
||||
}
|
||||
|
||||
// Keys are derived from labels so the builder only ever asks for a label.
|
||||
// Duplicates and empties are made safe here, matching the server schema.
|
||||
function deriveKeys(fields: FormField[]): FormField[] {
|
||||
const seen = new Set<string>()
|
||||
return fields.map((f, i) => {
|
||||
const base = slugify(f.label || '', '_') || `field_${i + 1}`
|
||||
let key = base
|
||||
let n = 2
|
||||
while (seen.has(key)) key = `${base}_${n++}`
|
||||
seen.add(key)
|
||||
return { ...f, key }
|
||||
})
|
||||
}
|
||||
const deriveKeys = (fields: FormField[]): FormField[] => deriveFieldKeys(fields)
|
||||
|
||||
export function FormBuilder({ initial }: { initial: BuilderForm }) {
|
||||
const router = useRouter()
|
||||
|
||||
Reference in New Issue
Block a user