All files triggers.ts

92.5% Statements 37/40
82.35% Branches 14/17
100% Functions 5/5
91.42% Lines 32/35

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 121 122 123 124 125 126 127 128                                                                    1x       4x   4x   2x           1x       1x                 1x       5x       1x       8x   7x 7x     7x     6x 3x 3x       3x 2x 2x     1x       1x       3x 2x       1x         6x 2x       5x 2x 2x 2x     2x     4x    
/**
 * WalkMe Triggers Module
 *
 * Tour trigger evaluation system.
 * Determines when a tour should be automatically activated.
 */
 
import type { Tour, TourTrigger } from '../types/walkme.types'
 
// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------
 
export interface TriggerEvaluationContext {
  /** Current page route/pathname */
  currentRoute: string
  /** Total number of page visits */
  visitCount: number
  /** ISO date string of first visit */
  firstVisitDate: string | null
  /** IDs of completed tours */
  completedTourIds: string[]
  /** Set of custom events that have been emitted */
  customEvents: Set<string>
}
 
// ---------------------------------------------------------------------------
// Main Evaluation
// ---------------------------------------------------------------------------
 
/**
 * Evaluate whether a tour should be triggered based on its trigger config
 * and the current context. Does NOT check conditions (that's a separate step).
 */
export function shouldTriggerTour(
  tour: Tour,
  context: TriggerEvaluationContext,
): boolean {
  const { trigger } = tour
 
  switch (trigger.type) {
    case 'onFirstVisit':
      return evaluateOnFirstVisit(trigger, context)
    case 'onRouteEnter':
      return evaluateOnRouteEnter(trigger, context)
    case 'onEvent':
      return evaluateOnEvent(trigger, context)
    case 'manual':
      return false // Manual tours are started programmatically only
    case 'scheduled':
      return evaluateScheduled(trigger, context)
    default:
      return false
  }
}
 
// ---------------------------------------------------------------------------
// Individual Trigger Evaluators
// ---------------------------------------------------------------------------
 
/** First visit: triggers only when visitCount === 1 */
export function evaluateOnFirstVisit(
  _trigger: TourTrigger,
  context: TriggerEvaluationContext,
): boolean {
  return context.visitCount === 1
}
 
/** Route enter: triggers when current route matches the trigger's route pattern */
export function evaluateOnRouteEnter(
  trigger: TourTrigger,
  context: TriggerEvaluationContext,
): boolean {
  if (!trigger.route) return false
 
  const pattern = trigger.route
  const route = context.currentRoute
 
  // Exact match
  if (pattern === route) return true
 
  // Wildcard match: /dashboard/* matches /dashboard/anything
  if (pattern.endsWith('/*')) {
    const prefix = pattern.slice(0, -2)
    return route.startsWith(prefix)
  }
 
  // Glob match: /dashboard/** matches /dashboard/a/b/c
  if (pattern.endsWith('/**')) {
    const prefix = pattern.slice(0, -3)
    return route.startsWith(prefix)
  }
 
  return false
}
 
/** Event trigger: activates when a specific custom event has been emitted */
export function evaluateOnEvent(
  trigger: TourTrigger,
  context: TriggerEvaluationContext,
): boolean {
  if (!trigger.event) return false
  return context.customEvents.has(trigger.event)
}
 
/** Scheduled trigger: activates after N visits or N days since first visit */
export function evaluateScheduled(
  trigger: TourTrigger,
  context: TriggerEvaluationContext,
): boolean {
  // Check visit-based threshold
  if (trigger.afterVisits !== undefined) {
    if (context.visitCount >= trigger.afterVisits) return true
  }
 
  // Check day-based threshold
  if (trigger.afterDays !== undefined && context.firstVisitDate) {
    const firstVisit = new Date(context.firstVisitDate)
    const now = new Date()
    const daysSinceFirstVisit = Math.floor(
      (now.getTime() - firstVisit.getTime()) / (1000 * 60 * 60 * 24),
    )
    if (daysSinceFirstVisit >= trigger.afterDays) return true
  }
 
  return false
}