To handle multi-step forms (wizards), model each step as a Vest group. This allows you to validate the current step independently while maintaining the state of previously completed steps.
When a user attempts to move to the next step, use .focus({ onlyGroup: stepName }) to run validation only for that specific group. This ensures that errors in future steps do not block progress in the current step.
Key considerations:
- Step Navigation: Use
result.isValidByGroup(stepName) to determine if a user can proceed. - Final Submission: Always run the full suite using
.run(data) at the end of the workflow to ensure the entire form is valid. - Top-level tests: The
onlyGroup option excludes ungrouped top-level tests. Ensure all step-specific requirements are contained within their respective groups.
import { create, enforce, group, test } from 'vest';
type Step = 'account' | 'profile' | 'billing';
type OnboardingData = {
displayName: string;
email: string;
plan: string;
};
// 1. Define the suite with groups representing steps
export const onboardingSuite = create<{
fields: keyof OnboardingData;
groups: Step;
}>((data: OnboardingData) => {
group('account', () => {
test('email', 'Email is required', () => {
enforce(data.email).isNotBlank();
});
});
group('profile', () => {
test('displayName', 'Display name is required', () => {
enforce(data.displayName).isNotBlank();
});
});
group('billing', () => {
test('plan', 'Choose a plan', () => {
enforce(data.plan).isNotBlank();
});
});
});
// 2. Validate only the current step for navigation
async function canContinue(step: Step, data: OnboardingData) {
const result = await onboardingSuite.focus({ onlyGroup: step }).run(data);
return result.isValidByGroup(step);
}
// 3. Validate the entire suite for final submission
async function submitForm(data: OnboardingData) {
const result = await onboardingSuite.run(data);
if (!result.isValid()) {
// Handle errors (e.g., navigate to first invalid step)
return;
}
// Proceed with submission
}