All files validation.ts

100% Statements 28/28
100% Branches 7/7
100% Functions 4/4
100% Lines 28/28

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120              1x             1x                 1x               1x                               24x 7x   17x             1x                       1x             1x     2x 2x 1x   1x       1x     2x 2x   2x 4x 4x 3x   1x       2x               1x     2x 2x 1x   1x    
/**
 * WalkMe Validation Module
 *
 * Zod schemas for runtime validation of tour configurations.
 * Ensures tour definitions are well-formed before they're used.
 */
 
import { z } from 'zod'
import type { Tour } from '../types/walkme.types'
 
// ---------------------------------------------------------------------------
// Schemas
// ---------------------------------------------------------------------------
 
export const TourTriggerSchema = z.object({
  type: z.enum(['onFirstVisit', 'onRouteEnter', 'onEvent', 'manual', 'scheduled']),
  delay: z.number().min(0).optional(),
  route: z.string().optional(),
  event: z.string().optional(),
  afterVisits: z.number().min(1).optional(),
  afterDays: z.number().min(0).optional(),
})
 
export const TourConditionsSchema = z.object({
  userRole: z.array(z.string()).optional(),
  featureFlags: z.array(z.string()).optional(),
  completedTours: z.array(z.string()).optional(),
  notCompletedTours: z.array(z.string()).optional(),
  custom: z.function().optional(),
}).optional()
 
export const TourStepSchema = z.object({
  id: z.string().min(1),
  type: z.enum(['tooltip', 'modal', 'spotlight', 'beacon', 'floating']),
  title: z.string().min(1),
  content: z.string(),
  target: z.string().optional(),
  route: z.string().optional(),
  position: z.enum(['top', 'bottom', 'left', 'right', 'auto']).optional(),
  actions: z.array(z.enum(['next', 'prev', 'skip', 'complete', 'close'])).min(1),
  delay: z.number().min(0).optional(),
  autoAdvance: z.number().min(0).optional(),
  beforeShow: z.function().optional(),
  afterShow: z.function().optional(),
}).refine(
  (step) => {
    // tooltip, spotlight, and beacon require a target
    if (['tooltip', 'spotlight', 'beacon'].includes(step.type)) {
      return !!step.target
    }
    return true
  },
  {
    message: 'Steps of type tooltip, spotlight, and beacon require a target selector',
  },
)
 
export const TourSchema = z.object({
  id: z.string().min(1),
  name: z.string().min(1),
  description: z.string().optional(),
  trigger: TourTriggerSchema,
  conditions: TourConditionsSchema,
  steps: z.array(TourStepSchema).min(1),
  onComplete: z.function().optional(),
  onSkip: z.function().optional(),
  priority: z.number().optional(),
})
 
export const TourArraySchema = z.array(TourSchema)
 
// ---------------------------------------------------------------------------
// Validation Functions
// ---------------------------------------------------------------------------
 
/** Validate a single tour configuration */
export function validateTour(
  tour: unknown,
): { valid: boolean; errors?: z.ZodError; tour?: Tour } {
  const result = TourSchema.safeParse(tour)
  if (result.success) {
    return { valid: true, tour: result.data as unknown as Tour }
  }
  return { valid: false, errors: result.error }
}
 
/** Validate an array of tours, returning only the valid ones */
export function validateTours(
  tours: unknown[],
): { valid: boolean; errors?: z.ZodError[]; validTours: Tour[] } {
  const validTours: Tour[] = []
  const errors: z.ZodError[] = []
 
  for (const tour of tours) {
    const result = TourSchema.safeParse(tour)
    if (result.success) {
      validTours.push(result.data as unknown as Tour)
    } else {
      errors.push(result.error)
    }
  }
 
  return {
    valid: errors.length === 0,
    errors: errors.length > 0 ? errors : undefined,
    validTours,
  }
}
 
/** Validate a single step configuration */
export function validateStep(
  step: unknown,
): { valid: boolean; errors?: z.ZodError } {
  const result = TourStepSchema.safeParse(step)
  if (result.success) {
    return { valid: true }
  }
  return { valid: false, errors: result.error }
}