Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,19 @@ function onFloatValueChange(this: INumericWidget, v: number) {
}
}

function computePrecisionFromResolution(
resolution: number | undefined,
defaultPrecision: number
): number {
if (!resolution || !Number.isFinite(resolution) || resolution <= 0) {
return defaultPrecision
}

const magnitude = Math.floor(Math.log10(resolution))
const precision = Math.max(0, -magnitude)
return precision
}
Comment on lines +28 to +39
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

LGTM! Consider exposing helper for testing.

The precision computation logic is correct and handles edge cases well (zero, negative, NaN, undefined). The logarithmic approach appropriately derives decimal places from resolution values.

However, the new helper isn't exposed in _for_testing, limiting direct unit test coverage.

💡 Optional: Export helper for better test coverage
 export const _for_testing = {
-  onFloatValueChange
+  onFloatValueChange,
+  computePrecisionFromResolution
 }

This would allow direct testing of edge cases like very small values (1e-10), boundary conditions, and invalid inputs without going through the full widget constructor.

🤖 Prompt for AI Agents
In src/renderer/extensions/vueNodes/widgets/composables/useFloatWidget.ts around
lines 28 to 39, the computePrecisionFromResolution helper is currently internal
and not exported for tests; export it (for example via a named export and also
re-export under a _for_testing object) so unit tests can directly import and
exercise edge cases (very small values, NaN, undefined, zero, negative) without
instantiating the whole widget; ensure the export is harmless in production
(keep existing default exports unchanged) and update any barrel/index exports if
necessary.


export const _for_testing = {
onFloatValueChange
}
Expand All @@ -49,10 +62,22 @@ export const useFloatWidget = () => {
? 'knob'
: 'number'

const step = inputSpec.step ?? 0.5
const roundOption =
typeof inputSpec.round === 'number' ? inputSpec.round : undefined
const hasExplicitStep = typeof inputSpec.step === 'number'
const step = hasExplicitStep ? (inputSpec.step as number) : 0.5
const resolution = roundOption ?? (hasExplicitStep ? step : undefined)

const autoPrecision = computePrecisionFromResolution(resolution, 3)

const floatRoundingPrecision =
settingStore.get('Comfy.FloatRoundingPrecision')
const precision =
settingStore.get('Comfy.FloatRoundingPrecision') ||
Math.max(0, -Math.floor(Math.log10(step)))
typeof floatRoundingPrecision === 'number' &&
floatRoundingPrecision > 0
? floatRoundingPrecision
: autoPrecision

const enableRounding = !settingStore.get('Comfy.DisableFloatRounding')

/** Assertion {@link inputSpec.default} */
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,29 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'

import { _for_testing } from '@/renderer/extensions/vueNodes/widgets/composables/useFloatWidget'
import {
_for_testing,
useFloatWidget
} from '@/renderer/extensions/vueNodes/widgets/composables/useFloatWidget'
import type { InputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'

let mockFloatRoundingPrecision = 0

vi.mock('@/scripts/widgets', () => ({
addValueControlWidgets: vi.fn()
addValueControlWidgets: vi.fn(),
addValueControlWidget: vi.fn()
}))

vi.mock('@/platform/settings/settingStore', () => ({
useSettingStore: () => ({
settings: {}
settings: {},
get: (key: string) => {
if (key === 'Comfy.FloatRoundingPrecision')
return mockFloatRoundingPrecision
if (key === 'Comfy.DisableFloatRounding') return false
if (key === 'Comfy.DisableSliders') return false
return undefined
}
})
}))

Expand All @@ -24,6 +39,7 @@ describe('useFloatWidget', () => {
options: {},
value: 0
}
mockFloatRoundingPrecision = 0
})

it('should not round values when round option is not set', () => {
Expand Down Expand Up @@ -77,4 +93,80 @@ describe('useFloatWidget', () => {
expect(widget.value).toBeCloseTo(3, 10)
})
})

describe('widget constructor precision', () => {
const buildNode = () => {
const widgets: any[] = []
const node: LGraphNode = {
addWidget: (
type: string,
name: string,
value: number,
callback: (v: number) => void,
options: any
) => {
const widget = { type, name, value, callback, options }
widgets.push(widget)
return widget
}
} as unknown as LGraphNode

return { node, widgets }
}

it('uses auto precision derived from step when provided', () => {
const { node, widgets } = buildNode()
const widgetConstructor = useFloatWidget()

const inputSpec = {
type: 'FLOAT',
name: 'test-float',
step: 0.01,
min: 0,
max: 1
} as unknown as InputSpec

widgetConstructor(node, inputSpec)

expect(widgets).toHaveLength(1)
expect(widgets[0].options.precision).toBe(2)
})

it('falls back to default precision when step and round are not provided', () => {
const { node, widgets } = buildNode()
const widgetConstructor = useFloatWidget()

const inputSpec = {
type: 'FLOAT',
name: 'test-float',
min: 0,
max: 1
} as unknown as InputSpec

widgetConstructor(node, inputSpec)

expect(widgets).toHaveLength(1)
expect(widgets[0].options.precision).toBe(3)
})

it('respects user FloatRoundingPrecision setting when > 0', () => {
const { node, widgets } = buildNode()
const widgetConstructor = useFloatWidget()

mockFloatRoundingPrecision = 4

const inputSpec = {
type: 'FLOAT',
name: 'test-float',
step: 0.01,
min: 0,
max: 1
} as unknown as InputSpec

widgetConstructor(node, inputSpec)

expect(widgets).toHaveLength(1)
expect(widgets[0].options.precision).toBe(4)
})
})
})