mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
## What kind of change does this PR introduce? Refactor. Extracts zod number input preprocessing into a shared Studio helper and migrates existing call sites. ## What is the current behavior? Number input clearing logic for controlled `type="number"` fields is duplicated across Studio forms. The pipeline destination form had local helpers, while Auth and settings forms inline the same `z.preprocess` pattern. ## What is the new behavior? Adds `apps/studio/lib/forms/zod-number-input.ts` with: - `requiredNumberInputSchema` for required `z.number()` fields (`''` → `NaN`) - `optionalNumberInputSchema` for optional number fields (`''`/`null` → `undefined`) - `preprocessEmptyNumberInput` for `z.coerce.number()` schemas Migrates: - Pipeline destination form - Connection pooling settings - Auth provider validation (email and SMS OTP fields) - SMTP settings - MFA settings - Protection settings ## To test 1. Open **Authentication > Providers > Email**, clear **Email OTP expiration** or **Minimum password length**, and confirm the field stays empty and shows validation on save. 2. Open **Authentication > Providers > Phone**, select a provider, clear **SMS OTP Expiry**, and confirm validation still works. 3. Open **Authentication > Emails > SMTP settings**, clear **Port** or **Rate limit**, and confirm validation on save. 4. Open **Authentication > Multi-factor authentication**, clear **Max enrolled factors** or **Phone OTP length**, and confirm validation on save. 5. Open **Database > Replication**, start a new pipeline, select **BigQuery**, expand **Advanced settings**, clear a required numeric field, and confirm it stays empty until filled. 6. (Optional, hosted non-HA projects only) Open **Database > Settings**, scroll to **Connection pooling**, clear **Connection pool size**, and confirm the value does not snap back before save. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved handling of empty numeric fields across authentication, SMTP, database replication, and connection pooling settings. * Optional numeric settings can now be cleared without triggering unnecessary validation errors. * Required numeric fields continue to display appropriate validation errors when left incomplete. * Standardized validation behavior for OTP settings, password requirements, SMTP limits, replication configuration, and pool sizing. * Refined protection settings to focus on CAPTCHA and leaked-password options. * **Tests** * Added coverage for empty and optional numeric input behavior. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
96 lines
2.7 KiB
TypeScript
96 lines
2.7 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import * as z from 'zod'
|
|
|
|
import {
|
|
optionalNumberInputSchema,
|
|
preprocessEmptyNumberInput,
|
|
requiredNumberInputSchema,
|
|
} from './zod-number-input'
|
|
|
|
const REQUIRED_ERROR = 'Value is required'
|
|
|
|
describe('requiredNumberInputSchema', () => {
|
|
const schema = requiredNumberInputSchema(
|
|
z
|
|
.number({
|
|
required_error: REQUIRED_ERROR,
|
|
invalid_type_error: REQUIRED_ERROR,
|
|
})
|
|
.min(1, 'Must be greater than 0')
|
|
)
|
|
|
|
it('rejects an empty string with the schema error message', () => {
|
|
const result = schema.safeParse('')
|
|
|
|
expect(result.success).toBe(false)
|
|
if (!result.success) expect(result.error.issues[0]?.message).toBe(REQUIRED_ERROR)
|
|
})
|
|
|
|
it('leaves undefined to the inner schema', () => {
|
|
const result = schema.safeParse(undefined)
|
|
|
|
expect(result.success).toBe(false)
|
|
if (!result.success) expect(result.error.issues[0]?.message).toBe(REQUIRED_ERROR)
|
|
})
|
|
|
|
it('accepts a valid number', () => {
|
|
expect(schema.safeParse(2).success).toBe(true)
|
|
})
|
|
})
|
|
|
|
describe('optionalNumberInputSchema', () => {
|
|
const schema = optionalNumberInputSchema(
|
|
z.number().int('Must be a whole number').min(0, 'Must be 0 or greater')
|
|
)
|
|
|
|
it.each(['', null, undefined])('normalizes %s to undefined', (value) => {
|
|
const result = schema.safeParse(value)
|
|
|
|
expect(result.success).toBe(true)
|
|
if (result.success) expect(result.data).toBeUndefined()
|
|
})
|
|
|
|
it('accepts a valid number', () => {
|
|
const result = schema.safeParse(5)
|
|
|
|
expect(result.success).toBe(true)
|
|
if (result.success) expect(result.data).toBe(5)
|
|
})
|
|
})
|
|
|
|
describe('preprocessEmptyNumberInput', () => {
|
|
const schema = preprocessEmptyNumberInput(
|
|
z.coerce
|
|
.number({ required_error: REQUIRED_ERROR, invalid_type_error: REQUIRED_ERROR })
|
|
.min(0, 'Must be 0 or larger')
|
|
)
|
|
|
|
it('rejects an empty string with the schema error message', () => {
|
|
const result = schema.safeParse('')
|
|
|
|
expect(result.success).toBe(false)
|
|
if (!result.success) expect(result.error.issues[0]?.message).toBe(REQUIRED_ERROR)
|
|
})
|
|
|
|
it('coerces a numeric string', () => {
|
|
const result = schema.safeParse('12')
|
|
|
|
expect(result.success).toBe(true)
|
|
if (result.success) expect(result.data).toBe(12)
|
|
})
|
|
|
|
it('accepts an empty string when the inner schema is optional', () => {
|
|
const optionalSchema = preprocessEmptyNumberInput(
|
|
z.coerce
|
|
.number({ required_error: REQUIRED_ERROR, invalid_type_error: REQUIRED_ERROR })
|
|
.min(0, 'Must be 0 or larger')
|
|
.optional()
|
|
)
|
|
|
|
const result = optionalSchema.safeParse('')
|
|
|
|
expect(result.success).toBe(true)
|
|
if (result.success) expect(result.data).toBeUndefined()
|
|
})
|
|
})
|