All files utils.ts

88.13% Statements 156/177
90.9% Branches 20/22
100% Functions 4/4
88.13% Lines 156/177

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 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 1791x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 76x 76x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 38x 35x 35x 35x 3x 38x                     3x 3x 3x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 38x 35x 35x 3x 38x                       38x 3x 3x 3x 3x 3x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 27x 12x 12x 12x 12x 12x 12x 15x 15x 15x 38x 15x 15x 15x 15x 15x 15x 38x 15x 15x 27x 2x 2x 2x 2x 2x 2x 13x 27x 3x 3x 3x 3x 3x 3x 10x 27x 2x 2x 2x 2x 2x 2x 8x 8x 8x 8x 8x 8x 8x    
import type { VNode } from 'vue'
import type { SizeToEnvironmentMap, IconSizeMap } from './types'
import FzButton from './FzButton.vue'
 
/**
 * Minimum number of FzButton components required in FzButtonGroup slot.
 * 
 * FzButtonGroup enforces a minimum of 2 buttons to ensure proper visual balance
 * and consistent user experience across form actions.
 */
const MIN_BUTTONS = 2
 
/**
 * Maximum number of FzButton components allowed in FzButtonGroup slot.
 * 
 * FzButtonGroup enforces a maximum of 3 buttons to prevent visual clutter
 * and maintain optimal spacing with the fixed 16px gap layout.
 */
const MAX_BUTTONS = 3
 
/**
 * Maps deprecated ButtonSize to ButtonEnvironment
 * 
 * Used for backward compatibility when size prop is provided instead of environment.
 * All sizes map to environments: xs/sm/md → backoffice, lg → frontoffice
 */
export const sizeToEnvironmentMapping: SizeToEnvironmentMap = {
  xs: 'backoffice',
  sm: 'backoffice',
  md: 'backoffice',
  lg: 'frontoffice'
}
 
/**
 * Maps ButtonSize to IconSize for FzIconButton
 * 
 * Used by FzIconButton to determine icon size based on button size.
 */
export const iconSizeMap: IconSizeMap = {
  xs: 'sm',
  sm: 'md',
  md: 'lg',
  lg: 'lg'
}
 
/**
 * Checks if a VNode represents a FzButton component.
 * 
 * @param vnode - The VNode to check
 * @returns true if the VNode is a FzButton component
 */
export function isButtonComponent(vnode: VNode): boolean {
  return vnode.type === FzButton
}
 
/**
 * Recursively collects all FzButton components from a VNode tree.
 * 
 * Handles Vue Fragment nodes created by v-if, v-else, v-for directives.
 * 
 * @param vnode - The VNode to analyze recursively
 * @param buttons - Array to collect found FzButton components
 * @returns array of all FzButton VNodes found at any depth
 */
export function collectButtons(vnode: VNode, buttons: VNode[] = []): VNode[] {
  if (isButtonComponent(vnode)) {
    buttons.push(vnode)
    return buttons
  }
  
  if (typeof vnode.type === 'symbol') {
    const children = vnode.children
    
    if (Array.isArray(children)) {
      children.forEach((child) => {
        if (typeof child === 'object' && child !== null && 'type' in child) {
          collectButtons(child as VNode, buttons)
        }
      })
    }
  }
  
  return buttons
}
 
/**
 * Recursively collects all non-FzButton elements from a VNode tree.
 * 
 * Used to detect invalid elements (HTML elements, other Vue components, text nodes).
 * 
 * @param vnode - The VNode to analyze recursively
 * @param invalidElements - Array to collect found invalid elements
 * @returns array of all non-FzButton VNodes found at any depth
 */
export function collectInvalidElements(vnode: VNode, invalidElements: VNode[] = []): VNode[] {
  if (isButtonComponent(vnode)) {
    return invalidElements
  }
  
  if (typeof vnode.type === 'symbol') {
    const children = vnode.children
    
    if (Array.isArray(children)) {
      children.forEach((child) => {
        if (typeof child === 'object' && child !== null && 'type' in child) {
          collectInvalidElements(child as VNode, invalidElements)
        } else if (typeof child === 'string' && child.trim() !== '') {
          invalidElements.push(child as any)
        }
      })
    }
  } else {
    invalidElements.push(vnode)
  }
  
  return invalidElements
}
 
/**
 * Validates FzButtonGroup slot content.
 * 
 * Ensures slot contains only FzButton components within MIN_BUTTONS-MAX_BUTTONS range.
 * 
 * @param vnodes - Array of VNodes from the slot to validate
 * @returns Object with validation status, button count, and error message if invalid
 */
export function validateButtonGroupSlot(vnodes: VNode[]): { valid: boolean; buttonCount: number; error: string | null } {
  if (!vnodes || vnodes.length === 0) {
    return {
      valid: false,
      buttonCount: 0,
      error: `[FzButtonGroup] Slot is empty. Expected ${MIN_BUTTONS}-${MAX_BUTTONS} FzButton components. Only FzButton components are allowed.`
    }
  }
  
  const buttons: VNode[] = []
  vnodes.forEach((vnode) => {
    collectButtons(vnode, buttons)
  })
  
  const buttonCount = buttons.length
  
  const invalidElements: VNode[] = []
  vnodes.forEach((vnode) => {
    collectInvalidElements(vnode, invalidElements)
  })
  
  if (invalidElements.length > 0) {
    return {
      valid: false,
      buttonCount,
      error: `[FzButtonGroup] Slot contains invalid elements. Only FzButton components are allowed (found ${buttonCount} FzButton, ${invalidElements.length} invalid element(s)).`
    }
  }
  
  if (buttonCount < MIN_BUTTONS) {
    return {
      valid: false,
      buttonCount,
      error: `[FzButtonGroup] Too few buttons. Expected ${MIN_BUTTONS}-${MAX_BUTTONS} FzButton components, found ${buttonCount}.`
    }
  }
  
  if (buttonCount > MAX_BUTTONS) {
    return {
      valid: false,
      buttonCount,
      error: `[FzButtonGroup] Too many buttons. Expected ${MIN_BUTTONS}-${MAX_BUTTONS} FzButton components, found ${buttonCount}.`
    }
  }
  
  return {
    valid: true,
    buttonCount,
    error: null
  }
}