fix: tighten CEF scale helpers
- commit
- 0fa06a1116f716446411f33a6c6e34ae76eeff80
- author
- brice · · 2026-05-16 15:07 UTC
- parent
- b6c23e2
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
5 files · +67 -33
M internal/infrastructure/cef/context_menu.go +1 -2
| @@ -8,7 +8,6 @@ import ( | |||
| 8 | 8 | purecef "github.com/bnema/purego-cef/cef" | |
| 9 | 9 | ||
| 10 | 10 | "github.com/bnema/dumber/internal/application/port" | |
| 11 | - | "github.com/bnema/dumber/internal/infrastructure/gtkutil" | |
| 12 | 11 | "github.com/bnema/dumber/internal/logging" | |
| 13 | 12 | "github.com/bnema/puregotk/v4/gtk" | |
| 14 | 13 | ) | |
| @@ -134,7 +133,7 @@ func contextMenuAnchorPosition(params purecef.ContextMenuParams, scale, backingS | |||
| 134 | 133 | if backingScale <= 1 { | |
| 135 | 134 | return params.GetXcoord(), params.GetYcoord() | |
| 136 | 135 | } | |
| 137 | - | return gtkutil.DeviceToLogical(params.GetXcoord(), scale), gtkutil.DeviceToLogical(params.GetYcoord(), scale) | |
| 136 | + | return deviceToLogicalCoord(params.GetXcoord(), scale), deviceToLogicalCoord(params.GetYcoord(), scale) | |
| 138 | 137 | } | |
| 139 | 138 | ||
| 140 | 139 | func logContextMenuPopupRequest( | |
| @@ -8,7 +8,6 @@ import ( | |||||
| 8 | purecef "github.com/bnema/purego-cef/cef" | 8 | purecef "github.com/bnema/purego-cef/cef" | ||
| 9 | 9 | ||||
| 10 | "github.com/bnema/dumber/internal/application/port" | 10 | "github.com/bnema/dumber/internal/application/port" | ||
| 11 | - | "github.com/bnema/dumber/internal/infrastructure/gtkutil" | |||
| 12 | "github.com/bnema/dumber/internal/logging" | 11 | "github.com/bnema/dumber/internal/logging" | ||
| 13 | "github.com/bnema/puregotk/v4/gtk" | 12 | "github.com/bnema/puregotk/v4/gtk" | ||
| 14 | ) | 13 | ) | ||
| @@ -134,7 +133,7 @@ func contextMenuAnchorPosition(params purecef.ContextMenuParams, scale, backingS | |||||
| 134 | if backingScale <= 1 { | 133 | if backingScale <= 1 { | ||
| 135 | return params.GetXcoord(), params.GetYcoord() | 134 | return params.GetXcoord(), params.GetYcoord() | ||
| 136 | } | 135 | } | ||
| 137 | - | return gtkutil.DeviceToLogical(params.GetXcoord(), scale), gtkutil.DeviceToLogical(params.GetYcoord(), scale) | 136 | + | return deviceToLogicalCoord(params.GetXcoord(), scale), deviceToLogicalCoord(params.GetYcoord(), scale) |
| 138 | } | 137 | } | ||
| 139 | 138 | ||||
| 140 | func logContextMenuPopupRequest( | 139 | func logContextMenuPopupRequest( | ||
A internal/infrastructure/cef/scale.go +14 -0
| @@ -0,0 +1,14 @@ | |||
| 1 | + | package cef | |
| 2 | + | ||
| 3 | + | import "math" | |
| 4 | + | ||
| 5 | + | func normalizeScale(scale float64) float64 { | |
| 6 | + | if math.IsNaN(scale) || math.IsInf(scale, 0) || scale <= 0 { | |
| 7 | + | return 1 | |
| 8 | + | } | |
| 9 | + | return scale | |
| 10 | + | } | |
| 11 | + | ||
| 12 | + | func deviceToLogicalCoord(value int32, scale float64) int32 { | |
| 13 | + | return int32(math.Floor(float64(value) / normalizeScale(scale))) | |
| 14 | + | } | |
| @@ -0,0 +1,14 @@ | |||||
| 1 | + | package cef | |||
| 2 | + | ||||
| 3 | + | import "math" | |||
| 4 | + | ||||
| 5 | + | func normalizeScale(scale float64) float64 { | |||
| 6 | + | if math.IsNaN(scale) || math.IsInf(scale, 0) || scale <= 0 { | |||
| 7 | + | return 1 | |||
| 8 | + | } | |||
| 9 | + | return scale | |||
| 10 | + | } | |||
| 11 | + | ||||
| 12 | + | func deviceToLogicalCoord(value int32, scale float64) int32 { | |||
| 13 | + | return int32(math.Floor(float64(value) / normalizeScale(scale))) | |||
| 14 | + | } | |||
M internal/infrastructure/cef/webview.go +22 -19
| @@ -20,7 +20,6 @@ import ( | |||
| 20 | 20 | ||
| 21 | 21 | "github.com/bnema/dumber/internal/application/dto" | |
| 22 | 22 | "github.com/bnema/dumber/internal/application/port" | |
| 23 | - | "github.com/bnema/dumber/internal/infrastructure/gtkutil" | |
| 24 | 23 | "github.com/bnema/dumber/internal/logging" | |
| 25 | 24 | ) | |
| 26 | 25 | ||
| @@ -445,11 +444,27 @@ func cefZoomFromFactor(factor float64) float64 { | |||
| 445 | 444 | } | |
| 446 | 445 | ||
| 447 | 446 | func cefZoomFromPageAndBackingFactor(pageZoom, backingScale float64) float64 { | |
| 448 | - | return cefZoomFromFactor(pageZoom / gtkutil.NormalizeScale(backingScale)) | |
| 447 | + | return cefZoomFromFactor(pageZoom / normalizeScale(backingScale)) | |
| 449 | 448 | } | |
| 450 | 449 | ||
| 451 | 450 | func pageZoomFromCEFAndBackingLevel(level, backingScale float64) float64 { | |
| 452 | - | return factorFromCEFZoom(level) * gtkutil.NormalizeScale(backingScale) | |
| 451 | + | return factorFromCEFZoom(level) * normalizeScale(backingScale) | |
| 452 | + | } | |
| 453 | + | ||
| 454 | + | func (wv *WebView) applyCEFZoomLevel(host purecef.BrowserHost, factor, cefLevel float64) { | |
| 455 | + | host.SetZoomLevel(cefLevel) | |
| 456 | + | // Force CEF to produce a new frame at the new zoom level. In OSR mode, | |
| 457 | + | // SetZoomLevel changes the Blink layout zoom but doesn't guarantee a | |
| 458 | + | // repaint. WasResized is a no-op when view dimensions haven't changed. | |
| 459 | + | // NotifyScreenInfoChanged forces surface ID invalidation + a full | |
| 460 | + | // SynchronizeVisualProperties cycle, which makes the renderer produce | |
| 461 | + | // a new compositor frame at the new zoom level. | |
| 462 | + | host.NotifyScreenInfoChanged() | |
| 463 | + | // Zoom is applied asynchronously in the renderer process. Request a couple | |
| 464 | + | // of follow-up refreshes on the CEF UI thread so OSR captures the updated | |
| 465 | + | // compositor frame after the zoom IPC has been processed. | |
| 466 | + | wv.scheduleZoomRefresh() | |
| 467 | + | wv.scheduleZoomReadback(factor, cefLevel) | |
| 453 | 468 | } | |
| 454 | 469 | ||
| 455 | 470 | // factorFromCEFZoom converts a Chromium/CEF logarithmic zoom level back to a | |
| @@ -484,20 +499,8 @@ func (wv *WebView) SetZoomLevel(_ context.Context, factor float64) error { | |||
| 484 | 499 | Float64("cef_level", cefLevel). | |
| 485 | 500 | Float64("osr_backing_scale", backingScale). | |
| 486 | 501 | Msg("cef: SetZoomLevel") | |
| 487 | - | host.SetZoomLevel(cefLevel) | |
| 502 | + | wv.applyCEFZoomLevel(host, factor, cefLevel) | |
| 488 | 503 | wv.zoomFactor.Store(factor) | |
| 489 | - | // Force CEF to produce a new frame at the new zoom level. In OSR mode, | |
| 490 | - | // SetZoomLevel changes the Blink layout zoom but doesn't guarantee a | |
| 491 | - | // repaint. WasResized is a no-op when view dimensions haven't changed. | |
| 492 | - | // NotifyScreenInfoChanged forces surface ID invalidation + a full | |
| 493 | - | // SynchronizeVisualProperties cycle, which makes the renderer produce | |
| 494 | - | // a new compositor frame at the new zoom level. | |
| 495 | - | host.NotifyScreenInfoChanged() | |
| 496 | - | // Zoom is applied asynchronously in the renderer process. Request a couple | |
| 497 | - | // of follow-up refreshes on the CEF UI thread so OSR captures the updated | |
| 498 | - | // compositor frame after the zoom IPC has been processed. | |
| 499 | - | wv.scheduleZoomRefresh() | |
| 500 | - | wv.scheduleZoomReadback(factor, cefLevel) | |
| 501 | 504 | return nil | |
| 502 | 505 | } | |
| 503 | 506 | ||
| @@ -1391,14 +1394,14 @@ func (wv *WebView) viewBridgeScale() float64 { | |||
| 1391 | 1394 | if wv == nil || wv.viewBridge == nil { | |
| 1392 | 1395 | return 1 | |
| 1393 | 1396 | } | |
| 1394 | - | return gtkutil.NormalizeScale(float64(wv.viewBridge.DeviceScaleFactor())) | |
| 1397 | + | return normalizeScale(float64(wv.viewBridge.DeviceScaleFactor())) | |
| 1395 | 1398 | } | |
| 1396 | 1399 | ||
| 1397 | 1400 | func (wv *WebView) osrBackingScaleFactor() float64 { | |
| 1398 | 1401 | if wv == nil || wv.viewBridge == nil { | |
| 1399 | 1402 | return 1 | |
| 1400 | 1403 | } | |
| 1401 | - | return gtkutil.NormalizeScale(wv.viewBridge.OSRBackingScaleFactor()) | |
| 1404 | + | return normalizeScale(wv.viewBridge.OSRBackingScaleFactor()) | |
| 1402 | 1405 | } | |
| 1403 | 1406 | ||
| 1404 | 1407 | func (wv *WebView) handleMiddleClickFromBridge() bool { | |
| @@ -1679,7 +1682,7 @@ func (wv *WebView) reapplyCurrentZoomForBackingScale(reason string) { | |||
| 1679 | 1682 | factor := wv.GetZoomLevel() | |
| 1680 | 1683 | backingScale := wv.osrBackingScaleFactor() | |
| 1681 | 1684 | cefLevel := cefZoomFromPageAndBackingFactor(factor, backingScale) | |
| 1682 | - | host.SetZoomLevel(cefLevel) | |
| 1685 | + | wv.applyCEFZoomLevel(host, factor, cefLevel) | |
| 1683 | 1686 | logging.FromContext(wv.ctx).Debug(). | |
| 1684 | 1687 | Str("reason", reason). | |
| 1685 | 1688 | Float64("factor", factor). | |
| @@ -20,7 +20,6 @@ import ( | |||||
| 20 | 20 | ||||
| 21 | "github.com/bnema/dumber/internal/application/dto" | 21 | "github.com/bnema/dumber/internal/application/dto" | ||
| 22 | "github.com/bnema/dumber/internal/application/port" | 22 | "github.com/bnema/dumber/internal/application/port" | ||
| 23 | - | "github.com/bnema/dumber/internal/infrastructure/gtkutil" | |||
| 24 | "github.com/bnema/dumber/internal/logging" | 23 | "github.com/bnema/dumber/internal/logging" | ||
| 25 | ) | 24 | ) | ||
| 26 | 25 | ||||
| @@ -445,11 +444,27 @@ func cefZoomFromFactor(factor float64) float64 { | |||||
| 445 | } | 444 | } | ||
| 446 | 445 | ||||
| 447 | func cefZoomFromPageAndBackingFactor(pageZoom, backingScale float64) float64 { | 446 | func cefZoomFromPageAndBackingFactor(pageZoom, backingScale float64) float64 { | ||
| 448 | - | return cefZoomFromFactor(pageZoom / gtkutil.NormalizeScale(backingScale)) | 447 | + | return cefZoomFromFactor(pageZoom / normalizeScale(backingScale)) |
| 449 | } | 448 | } | ||
| 450 | 449 | ||||
| 451 | func pageZoomFromCEFAndBackingLevel(level, backingScale float64) float64 { | 450 | func pageZoomFromCEFAndBackingLevel(level, backingScale float64) float64 { | ||
| 452 | - | return factorFromCEFZoom(level) * gtkutil.NormalizeScale(backingScale) | 451 | + | return factorFromCEFZoom(level) * normalizeScale(backingScale) |
| 452 | + | } | |||
| 453 | + | ||||
| 454 | + | func (wv *WebView) applyCEFZoomLevel(host purecef.BrowserHost, factor, cefLevel float64) { | |||
| 455 | + | host.SetZoomLevel(cefLevel) | |||
| 456 | + | // Force CEF to produce a new frame at the new zoom level. In OSR mode, | |||
| 457 | + | // SetZoomLevel changes the Blink layout zoom but doesn't guarantee a | |||
| 458 | + | // repaint. WasResized is a no-op when view dimensions haven't changed. | |||
| 459 | + | // NotifyScreenInfoChanged forces surface ID invalidation + a full | |||
| 460 | + | // SynchronizeVisualProperties cycle, which makes the renderer produce | |||
| 461 | + | // a new compositor frame at the new zoom level. | |||
| 462 | + | host.NotifyScreenInfoChanged() | |||
| 463 | + | // Zoom is applied asynchronously in the renderer process. Request a couple | |||
| 464 | + | // of follow-up refreshes on the CEF UI thread so OSR captures the updated | |||
| 465 | + | // compositor frame after the zoom IPC has been processed. | |||
| 466 | + | wv.scheduleZoomRefresh() | |||
| 467 | + | wv.scheduleZoomReadback(factor, cefLevel) | |||
| 453 | } | 468 | } | ||
| 454 | 469 | ||||
| 455 | // factorFromCEFZoom converts a Chromium/CEF logarithmic zoom level back to a | 470 | // factorFromCEFZoom converts a Chromium/CEF logarithmic zoom level back to a | ||
| @@ -484,20 +499,8 @@ func (wv *WebView) SetZoomLevel(_ context.Context, factor float64) error { | |||||
| 484 | Float64("cef_level", cefLevel). | 499 | Float64("cef_level", cefLevel). | ||
| 485 | Float64("osr_backing_scale", backingScale). | 500 | Float64("osr_backing_scale", backingScale). | ||
| 486 | Msg("cef: SetZoomLevel") | 501 | Msg("cef: SetZoomLevel") | ||
| 487 | - | host.SetZoomLevel(cefLevel) | 502 | + | wv.applyCEFZoomLevel(host, factor, cefLevel) |
| 488 | wv.zoomFactor.Store(factor) | 503 | wv.zoomFactor.Store(factor) | ||
| 489 | - | // Force CEF to produce a new frame at the new zoom level. In OSR mode, | |||
| 490 | - | // SetZoomLevel changes the Blink layout zoom but doesn't guarantee a | |||
| 491 | - | // repaint. WasResized is a no-op when view dimensions haven't changed. | |||
| 492 | - | // NotifyScreenInfoChanged forces surface ID invalidation + a full | |||
| 493 | - | // SynchronizeVisualProperties cycle, which makes the renderer produce | |||
| 494 | - | // a new compositor frame at the new zoom level. | |||
| 495 | - | host.NotifyScreenInfoChanged() | |||
| 496 | - | // Zoom is applied asynchronously in the renderer process. Request a couple | |||
| 497 | - | // of follow-up refreshes on the CEF UI thread so OSR captures the updated | |||
| 498 | - | // compositor frame after the zoom IPC has been processed. | |||
| 499 | - | wv.scheduleZoomRefresh() | |||
| 500 | - | wv.scheduleZoomReadback(factor, cefLevel) | |||
| 501 | return nil | 504 | return nil | ||
| 502 | } | 505 | } | ||
| 503 | 506 | ||||
| @@ -1391,14 +1394,14 @@ func (wv *WebView) viewBridgeScale() float64 { | |||||
| 1391 | if wv == nil || wv.viewBridge == nil { | 1394 | if wv == nil || wv.viewBridge == nil { | ||
| 1392 | return 1 | 1395 | return 1 | ||
| 1393 | } | 1396 | } | ||
| 1394 | - | return gtkutil.NormalizeScale(float64(wv.viewBridge.DeviceScaleFactor())) | 1397 | + | return normalizeScale(float64(wv.viewBridge.DeviceScaleFactor())) |
| 1395 | } | 1398 | } | ||
| 1396 | 1399 | ||||
| 1397 | func (wv *WebView) osrBackingScaleFactor() float64 { | 1400 | func (wv *WebView) osrBackingScaleFactor() float64 { | ||
| 1398 | if wv == nil || wv.viewBridge == nil { | 1401 | if wv == nil || wv.viewBridge == nil { | ||
| 1399 | return 1 | 1402 | return 1 | ||
| 1400 | } | 1403 | } | ||
| 1401 | - | return gtkutil.NormalizeScale(wv.viewBridge.OSRBackingScaleFactor()) | 1404 | + | return normalizeScale(wv.viewBridge.OSRBackingScaleFactor()) |
| 1402 | } | 1405 | } | ||
| 1403 | 1406 | ||||
| 1404 | func (wv *WebView) handleMiddleClickFromBridge() bool { | 1407 | func (wv *WebView) handleMiddleClickFromBridge() bool { | ||
| @@ -1679,7 +1682,7 @@ func (wv *WebView) reapplyCurrentZoomForBackingScale(reason string) { | |||||
| 1679 | factor := wv.GetZoomLevel() | 1682 | factor := wv.GetZoomLevel() | ||
| 1680 | backingScale := wv.osrBackingScaleFactor() | 1683 | backingScale := wv.osrBackingScaleFactor() | ||
| 1681 | cefLevel := cefZoomFromPageAndBackingFactor(factor, backingScale) | 1684 | cefLevel := cefZoomFromPageAndBackingFactor(factor, backingScale) | ||
| 1682 | - | host.SetZoomLevel(cefLevel) | 1685 | + | wv.applyCEFZoomLevel(host, factor, cefLevel) |
| 1683 | logging.FromContext(wv.ctx).Debug(). | 1686 | logging.FromContext(wv.ctx).Debug(). | ||
| 1684 | Str("reason", reason). | 1687 | Str("reason", reason). | ||
| 1685 | Float64("factor", factor). | 1688 | Float64("factor", factor). | ||
M internal/infrastructure/cef/webview_zoom_test.go +9 -7
| @@ -19,11 +19,13 @@ func TestZoomConversionsRoundTrip(t *testing.T) { | |||
| 19 | 19 | } | |
| 20 | 20 | ||
| 21 | 21 | func TestZoomConversionsCompensateOSRBackingScale(t *testing.T) { | |
| 22 | - | level := cefZoomFromPageAndBackingFactor(1.0, 1.2) | |
| 23 | - | assert.InDelta(t, 1.0/1.2, factorFromCEFZoom(level), 1e-9) | |
| 24 | - | assert.InDelta(t, 1.0, pageZoomFromCEFAndBackingLevel(level, 1.2), 1e-9) | |
| 25 | - | ||
| 26 | - | level = cefZoomFromPageAndBackingFactor(1.75, 1.2) | |
| 27 | - | assert.InDelta(t, 1.75/1.2, factorFromCEFZoom(level), 1e-9) | |
| 28 | - | assert.InDelta(t, 1.75, pageZoomFromCEFAndBackingLevel(level, 1.2), 1e-9) | |
| 22 | + | for _, backing := range []float64{1.0, 1.25, 1.5, 2.0} { | |
| 23 | + | for _, pageZoom := range []float64{1.0, 1.75} { | |
| 24 | + | t.Run(fmt.Sprintf("backing_%.2f_page_%.2f", backing, pageZoom), func(t *testing.T) { | |
| 25 | + | level := cefZoomFromPageAndBackingFactor(pageZoom, backing) | |
| 26 | + | assert.InDelta(t, pageZoom/backing, factorFromCEFZoom(level), 1e-9) | |
| 27 | + | assert.InDelta(t, pageZoom, pageZoomFromCEFAndBackingLevel(level, backing), 1e-9) | |
| 28 | + | }) | |
| 29 | + | } | |
| 30 | + | } | |
| 29 | 31 | } | |
| @@ -19,11 +19,13 @@ func TestZoomConversionsRoundTrip(t *testing.T) { | |||||
| 19 | } | 19 | } | ||
| 20 | 20 | ||||
| 21 | func TestZoomConversionsCompensateOSRBackingScale(t *testing.T) { | 21 | func TestZoomConversionsCompensateOSRBackingScale(t *testing.T) { | ||
| 22 | - | level := cefZoomFromPageAndBackingFactor(1.0, 1.2) | 22 | + | for _, backing := range []float64{1.0, 1.25, 1.5, 2.0} { |
| 23 | - | assert.InDelta(t, 1.0/1.2, factorFromCEFZoom(level), 1e-9) | 23 | + | for _, pageZoom := range []float64{1.0, 1.75} { |
| 24 | - | assert.InDelta(t, 1.0, pageZoomFromCEFAndBackingLevel(level, 1.2), 1e-9) | 24 | + | t.Run(fmt.Sprintf("backing_%.2f_page_%.2f", backing, pageZoom), func(t *testing.T) { |
| 25 | - | 25 | + | level := cefZoomFromPageAndBackingFactor(pageZoom, backing) | |
| 26 | - | level = cefZoomFromPageAndBackingFactor(1.75, 1.2) | 26 | + | assert.InDelta(t, pageZoom/backing, factorFromCEFZoom(level), 1e-9) |
| 27 | - | assert.InDelta(t, 1.75/1.2, factorFromCEFZoom(level), 1e-9) | 27 | + | assert.InDelta(t, pageZoom, pageZoomFromCEFAndBackingLevel(level, backing), 1e-9) |
| 28 | - | assert.InDelta(t, 1.75, pageZoomFromCEFAndBackingLevel(level, 1.2), 1e-9) | 28 | + | }) |
| 29 | + | } | |||
| 30 | + | } | |||
| 29 | } | 31 | } | ||
M internal/infrastructure/gtkutil/scale_test.go +21 -5
| @@ -1,13 +1,29 @@ | |||
| 1 | 1 | package gtkutil | |
| 2 | 2 | ||
| 3 | - | import "testing" | |
| 3 | + | import ( | |
| 4 | + | "math" | |
| 5 | + | "testing" | |
| 6 | + | ) | |
| 4 | 7 | ||
| 5 | 8 | func TestNormalizeScale(t *testing.T) { | |
| 6 | - | if got := NormalizeScale(0); got != 1 { | |
| 7 | - | t.Fatalf("NormalizeScale(0) = %v, want 1", got) | |
| 9 | + | tests := []struct { | |
| 10 | + | name string | |
| 11 | + | input float64 | |
| 12 | + | want float64 | |
| 13 | + | }{ | |
| 14 | + | {name: "zero", input: 0, want: 1}, | |
| 15 | + | {name: "fractional", input: 1.2, want: 1.2}, | |
| 16 | + | {name: "nan", input: math.NaN(), want: 1}, | |
| 17 | + | {name: "positive_infinity", input: math.Inf(1), want: 1}, | |
| 18 | + | {name: "negative_infinity", input: math.Inf(-1), want: 1}, | |
| 19 | + | {name: "negative", input: -1, want: 1}, | |
| 8 | 20 | } | |
| 9 | - | if got := NormalizeScale(1.2); got != 1.2 { | |
| 10 | - | t.Fatalf("NormalizeScale(1.2) = %v, want 1.2", got) | |
| 21 | + | for _, tt := range tests { | |
| 22 | + | t.Run(tt.name, func(t *testing.T) { | |
| 23 | + | if got := NormalizeScale(tt.input); got != tt.want { | |
| 24 | + | t.Fatalf("NormalizeScale(%v) = %v, want %v", tt.input, got, tt.want) | |
| 25 | + | } | |
| 26 | + | }) | |
| 11 | 27 | } | |
| 12 | 28 | } | |
| 13 | 29 | ||
| @@ -1,13 +1,29 @@ | |||||
| 1 | package gtkutil | 1 | package gtkutil | ||
| 2 | 2 | ||||
| 3 | - | import "testing" | 3 | + | import ( |
| 4 | + | "math" | |||
| 5 | + | "testing" | |||
| 6 | + | ) | |||
| 4 | 7 | ||||
| 5 | func TestNormalizeScale(t *testing.T) { | 8 | func TestNormalizeScale(t *testing.T) { | ||
| 6 | - | if got := NormalizeScale(0); got != 1 { | 9 | + | tests := []struct { |
| 7 | - | t.Fatalf("NormalizeScale(0) = %v, want 1", got) | 10 | + | name string |
| 11 | + | input float64 | |||
| 12 | + | want float64 | |||
| 13 | + | }{ | |||
| 14 | + | {name: "zero", input: 0, want: 1}, | |||
| 15 | + | {name: "fractional", input: 1.2, want: 1.2}, | |||
| 16 | + | {name: "nan", input: math.NaN(), want: 1}, | |||
| 17 | + | {name: "positive_infinity", input: math.Inf(1), want: 1}, | |||
| 18 | + | {name: "negative_infinity", input: math.Inf(-1), want: 1}, | |||
| 19 | + | {name: "negative", input: -1, want: 1}, | |||
| 8 | } | 20 | } | ||
| 9 | - | if got := NormalizeScale(1.2); got != 1.2 { | 21 | + | for _, tt := range tests { |
| 10 | - | t.Fatalf("NormalizeScale(1.2) = %v, want 1.2", got) | 22 | + | t.Run(tt.name, func(t *testing.T) { |
| 23 | + | if got := NormalizeScale(tt.input); got != tt.want { | |||
| 24 | + | t.Fatalf("NormalizeScale(%v) = %v, want %v", tt.input, got, tt.want) | |||
| 25 | + | } | |||
| 26 | + | }) | |||
| 11 | } | 27 | } | ||
| 12 | } | 28 | } | ||
| 13 | 29 | ||||