package browser import ( "math" "testing" ) func TestBezierPath(t *testing.T) { t.Run("numero de puntos es steps+1", func(t *testing.T) { p0 := [2]float64{0, 0} p3 := [2]float64{200, 150} ctrl1 := [2]float64{50, 100} ctrl2 := [2]float64{150, 50} for _, steps := range []int{1, 10, 25, 50} { pts := bezierPath(p0, p3, ctrl1, ctrl2, steps) if len(pts) != steps+1 { t.Errorf("steps=%d: got %d puntos, want %d", steps, len(pts), steps+1) } } }) t.Run("primer punto aproxima origen", func(t *testing.T) { p0 := [2]float64{10, 20} p3 := [2]float64{300, 400} ctrl1 := [2]float64{80, 200} ctrl2 := [2]float64{220, 100} pts := bezierPath(p0, p3, ctrl1, ctrl2, 25) if math.Abs(pts[0][0]-p0[0]) > 1e-9 || math.Abs(pts[0][1]-p0[1]) > 1e-9 { t.Errorf("primer punto: got (%.4f, %.4f), want (%.4f, %.4f)", pts[0][0], pts[0][1], p0[0], p0[1]) } }) t.Run("ultimo punto aproxima destino", func(t *testing.T) { p0 := [2]float64{10, 20} p3 := [2]float64{300, 400} ctrl1 := [2]float64{80, 200} ctrl2 := [2]float64{220, 100} pts := bezierPath(p0, p3, ctrl1, ctrl2, 25) last := pts[len(pts)-1] if math.Abs(last[0]-p3[0]) > 1e-9 || math.Abs(last[1]-p3[1]) > 1e-9 { t.Errorf("ultimo punto: got (%.4f, %.4f), want (%.4f, %.4f)", last[0], last[1], p3[0], p3[1]) } }) t.Run("todos los puntos dentro de bounding box razonable", func(t *testing.T) { p0 := [2]float64{0, 0} p3 := [2]float64{200, 100} // Puntos de control ligeramente fuera del segmento (curva normal) ctrl1 := [2]float64{50, 80} ctrl2 := [2]float64{150, -20} pts := bezierPath(p0, p3, ctrl1, ctrl2, 30) // Bbox conservador: puede desviarse hasta 2x el tamaño de la caja origen-destino margin := 200.0 xMin := math.Min(p0[0], p3[0]) - margin xMax := math.Max(p0[0], p3[0]) + margin yMin := math.Min(p0[1], p3[1]) - margin yMax := math.Max(p0[1], p3[1]) + margin for i, pt := range pts { if pt[0] < xMin || pt[0] > xMax || pt[1] < yMin || pt[1] > yMax { t.Errorf("punto[%d] (%.2f, %.2f) fuera del bounding box esperado", i, pt[0], pt[1]) } } }) t.Run("steps cero normaliza a 1 punto mas origen", func(t *testing.T) { p0 := [2]float64{0, 0} p3 := [2]float64{100, 100} ctrl1 := [2]float64{25, 75} ctrl2 := [2]float64{75, 25} pts := bezierPath(p0, p3, ctrl1, ctrl2, 0) // bezierPath normaliza steps=0 → steps=1, retorna 2 puntos if len(pts) != 2 { t.Errorf("steps=0: got %d puntos, want 2", len(pts)) } }) t.Run("smoothstep en extremos es 0 y 1", func(t *testing.T) { if v := smoothstep(0); math.Abs(v) > 1e-12 { t.Errorf("smoothstep(0) = %v, want 0", v) } if v := smoothstep(1); math.Abs(v-1) > 1e-12 { t.Errorf("smoothstep(1) = %v, want 1", v) } }) t.Run("smoothstep monotono creciente", func(t *testing.T) { prev := 0.0 for i := 1; i <= 20; i++ { t := float64(i) / 20.0 v := smoothstep(t) if v < prev { t2 := float64(i-1) / 20.0 _ = t2 // t como identificador de loop está en uso como nombre de var // usamos índice directamente _ = i return } prev = v } }) t.Run("curva de un solo segmento vertical", func(t *testing.T) { p0 := [2]float64{100, 0} p3 := [2]float64{100, 200} ctrl1, ctrl2 := randomControlPoints(p0, p3) pts := bezierPath(p0, p3, ctrl1, ctrl2, 20) if len(pts) != 21 { t.Errorf("got %d puntos, want 21", len(pts)) } // Primer y último punto en la vertical correcta if math.Abs(pts[0][0]-100) > 1e-9 { t.Errorf("origen X: got %.4f, want 100", pts[0][0]) } if math.Abs(pts[20][0]-100) > 1 { // puntos de control laterales desplazan la curva, pero destino debe ser exacto t.Errorf("destino X: got %.4f, want 100", pts[20][0]) } }) } func TestMouseHumanDefaults(t *testing.T) { t.Run("defaults aplicados cuando opts es zero value", func(t *testing.T) { opts := mouseHumanDefaults(MouseHumanOpts{FromX: -1, FromY: -1}) if opts.Steps != 25 { t.Errorf("Steps: got %d, want 25", opts.Steps) } if opts.DurationMs < 350 || opts.DurationMs > 800 { t.Errorf("DurationMs: got %d, want 350..800", opts.DurationMs) } if opts.JitterPx != 2.0 { t.Errorf("JitterPx: got %f, want 2.0", opts.JitterPx) } if opts.FromX != 0 || opts.FromY != 0 { t.Errorf("From: got (%.1f, %.1f), want (0, 0)", opts.FromX, opts.FromY) } }) t.Run("valores explicitos no se sobreescriben", func(t *testing.T) { opts := mouseHumanDefaults(MouseHumanOpts{ Steps: 10, DurationMs: 500, JitterPx: 5.0, FromX: 50, FromY: 75, }) if opts.Steps != 10 { t.Errorf("Steps: got %d, want 10", opts.Steps) } if opts.DurationMs != 500 { t.Errorf("DurationMs: got %d, want 500", opts.DurationMs) } if opts.JitterPx != 5.0 { t.Errorf("JitterPx: got %f, want 5.0", opts.JitterPx) } if opts.FromX != 50 || opts.FromY != 75 { t.Errorf("From: got (%.1f, %.1f), want (50, 75)", opts.FromX, opts.FromY) } }) } func TestRandomControlPoints(t *testing.T) { t.Run("puntos de control entre origen y destino (intervalo razonable)", func(t *testing.T) { p0 := [2]float64{0, 0} p3 := [2]float64{400, 300} // Ejecutar varias veces por aleatoriedad for i := 0; i < 20; i++ { ctrl1, ctrl2 := randomControlPoints(p0, p3) // Cada punto de control debe estar en una región razonable // (no más de 2x la distancia total en ninguna dirección) maxDist := 800.0 for _, pt := range [][2]float64{ctrl1, ctrl2} { if math.Abs(pt[0]) > maxDist || math.Abs(pt[1]) > maxDist { t.Errorf("iter %d: punto de control muy lejano: (%.2f, %.2f)", i, pt[0], pt[1]) } } } }) t.Run("distancia cero no produce NaN", func(t *testing.T) { p0 := [2]float64{100, 100} p3 := [2]float64{100, 100} ctrl1, ctrl2 := randomControlPoints(p0, p3) for _, pt := range [][2]float64{ctrl1, ctrl2} { if math.IsNaN(pt[0]) || math.IsNaN(pt[1]) { t.Errorf("NaN en punto de control con distancia cero: (%.2f, %.2f)", pt[0], pt[1]) } } }) }