Disable number grouping (thousands comma separators) by default in Vue node number widgets (#5776)

## Summary

Makes the
[useGrouping](https://primevue.org/inputnumber/#api.inputnumber.props.useGrouping)
prop for number widgets disabled by default, aligning with the old UI
(also requested via design). Node authors can still enable if they want
by setting prop explicitly.

## Changes

- **What**: Modified
[WidgetInputNumberInput](https://primevue.org/inputnumber/) to disable
`useGrouping` by default, requiring explicit opt-in via widget options
- **Testing**: Added component tests covering value binding, component
rendering, step calculations, and grouping behavior

## Review Focus

UX impact on existing nodes that may have relied on default grouping
behavior and test coverage for edge cases with precision calculations.

## Screenshots (if applicable)

*Before*:

<img width="1685" height="879" alt="Screenshot from 2025-09-25 11-34-34"
src="https://github.com/user-attachments/assets/432097ab-203d-4f86-8ca0-721b27ee33de"
/>

*After*:

<img width="1951" height="1175" alt="Screenshot from 2025-09-25
11-35-27"
src="https://github.com/user-attachments/assets/74d35b62-612e-4dbf-b6e2-0ac17af03ea1"
/>


┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5776-Disable-number-grouping-thousands-comma-separators-by-default-in-Vue-node-number-widget-2796d73d365081369ca6c155335d0d57)
by [Unito](https://www.unito.io)

---------

Co-authored-by: DrJKL <448862+DrJKL@users.noreply.github.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
Christian Byrne
2025-09-25 17:46:46 -07:00
committed by GitHub
parent 961af8731e
commit ac93a6ba3f
4 changed files with 214 additions and 0 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 KiB

View File

@@ -0,0 +1,208 @@
import { mount } from '@vue/test-utils'
import PrimeVue from 'primevue/config'
import InputNumber from 'primevue/inputnumber'
import { describe, expect, it } from 'vitest'
import type { SimplifiedWidget } from '@/types/simplifiedWidget'
import WidgetInputNumberInput from './WidgetInputNumberInput.vue'
function createMockWidget(
value: number = 0,
type: 'int' | 'float' = 'int',
options: SimplifiedWidget['options'] = {},
callback?: (value: number) => void
): SimplifiedWidget<number> {
return {
name: 'test_input_number',
type,
value,
options,
callback
}
}
function mountComponent(
widget: SimplifiedWidget<number>,
modelValue: number,
readonly = false
) {
return mount(WidgetInputNumberInput, {
global: {
plugins: [PrimeVue],
components: { InputNumber }
},
props: {
widget,
modelValue,
readonly
}
})
}
function getNumberInput(wrapper: ReturnType<typeof mount>) {
const input = wrapper.get<HTMLInputElement>('input[inputmode="numeric"]')
return input.element
}
describe('WidgetInputNumberInput Value Binding', () => {
it('displays initial value in input field', () => {
const widget = createMockWidget(42, 'int')
const wrapper = mountComponent(widget, 42)
const input = getNumberInput(wrapper)
expect(input.value).toBe('42')
})
it('emits update:modelValue when value changes', async () => {
const widget = createMockWidget(10, 'int')
const wrapper = mountComponent(widget, 10)
const inputNumber = wrapper.findComponent(InputNumber)
await inputNumber.vm.$emit('update:modelValue', 20)
const emitted = wrapper.emitted('update:modelValue')
expect(emitted).toBeDefined()
expect(emitted![0]).toContain(20)
})
it('handles negative values', () => {
const widget = createMockWidget(-5, 'int')
const wrapper = mountComponent(widget, -5)
const input = getNumberInput(wrapper)
expect(input.value).toBe('-5')
})
it('handles decimal values for float type', () => {
const widget = createMockWidget(3.14, 'float')
const wrapper = mountComponent(widget, 3.14)
const input = getNumberInput(wrapper)
expect(input.value).toBe('3.14')
})
})
describe('WidgetInputNumberInput Component Rendering', () => {
it('renders InputNumber component with show-buttons', () => {
const widget = createMockWidget(5, 'int')
const wrapper = mountComponent(widget, 5)
const inputNumber = wrapper.findComponent(InputNumber)
expect(inputNumber.exists()).toBe(true)
expect(inputNumber.props('showButtons')).toBe(true)
})
it('disables input when readonly', () => {
const widget = createMockWidget(5, 'int', {}, undefined)
const wrapper = mountComponent(widget, 5, true)
const inputNumber = wrapper.findComponent(InputNumber)
expect(inputNumber.props('disabled')).toBe(true)
})
it('sets button layout to horizontal', () => {
const widget = createMockWidget(5, 'int')
const wrapper = mountComponent(widget, 5)
const inputNumber = wrapper.findComponent(InputNumber)
expect(inputNumber.props('buttonLayout')).toBe('horizontal')
})
it('sets size to small', () => {
const widget = createMockWidget(5, 'int')
const wrapper = mountComponent(widget, 5)
const inputNumber = wrapper.findComponent(InputNumber)
expect(inputNumber.props('size')).toBe('small')
})
})
describe('WidgetInputNumberInput Step Value', () => {
it('defaults to 0 for unrestricted stepping', () => {
const widget = createMockWidget(5, 'int')
const wrapper = mountComponent(widget, 5)
const inputNumber = wrapper.findComponent(InputNumber)
expect(inputNumber.props('step')).toBe(0)
})
it('uses step2 value when provided', () => {
const widget = createMockWidget(5, 'int', { step2: 0.5 })
const wrapper = mountComponent(widget, 5)
const inputNumber = wrapper.findComponent(InputNumber)
expect(inputNumber.props('step')).toBe(0.5)
})
it('calculates step from precision for precision 0', () => {
const widget = createMockWidget(5, 'int', { precision: 0 })
const wrapper = mountComponent(widget, 5)
const inputNumber = wrapper.findComponent(InputNumber)
expect(inputNumber.props('step')).toBe(1)
})
it('calculates step from precision for precision 1', () => {
const widget = createMockWidget(5, 'float', { precision: 1 })
const wrapper = mountComponent(widget, 5)
const inputNumber = wrapper.findComponent(InputNumber)
expect(inputNumber.props('step')).toBe(0.1)
})
it('calculates step from precision for precision 2', () => {
const widget = createMockWidget(5, 'float', { precision: 2 })
const wrapper = mountComponent(widget, 5)
const inputNumber = wrapper.findComponent(InputNumber)
expect(inputNumber.props('step')).toBe(0.01)
})
})
describe('WidgetInputNumberInput Grouping Behavior', () => {
it('displays numbers without commas by default for int widgets', () => {
const widget = createMockWidget(1000, 'int')
const wrapper = mountComponent(widget, 1000)
const input = getNumberInput(wrapper)
expect(input.value).toBe('1000')
expect(input.value).not.toContain(',')
})
it('displays numbers without commas by default for float widgets', () => {
const widget = createMockWidget(1000.5, 'float')
const wrapper = mountComponent(widget, 1000.5)
const input = getNumberInput(wrapper)
expect(input.value).toBe('1000.5')
expect(input.value).not.toContain(',')
})
it('displays numbers with commas when grouping enabled', () => {
const widget = createMockWidget(1000, 'int', { useGrouping: true })
const wrapper = mountComponent(widget, 1000)
const input = getNumberInput(wrapper)
expect(input.value).toBe('1,000')
expect(input.value).toContain(',')
})
it('displays numbers without commas when grouping explicitly disabled', () => {
const widget = createMockWidget(1000, 'int', { useGrouping: false })
const wrapper = mountComponent(widget, 1000)
const input = getNumberInput(wrapper)
expect(input.value).toBe('1000')
expect(input.value).not.toContain(',')
})
it('displays numbers without commas when useGrouping option is undefined', () => {
const widget = createMockWidget(1000, 'int', { useGrouping: undefined })
const wrapper = mountComponent(widget, 1000)
const input = getNumberInput(wrapper)
expect(input.value).toBe('1000')
expect(input.value).not.toContain(',')
})
})

View File

@@ -48,6 +48,11 @@ const stepValue = computed(() => {
// Default to 'any' for unrestricted stepping
return 0
})
// Disable grouping separators by default unless explicitly enabled by the node author
const useGrouping = computed(() => {
return props.widget.options?.useGrouping === true
})
</script>
<template>
@@ -60,6 +65,7 @@ const stepValue = computed(() => {
size="small"
:disabled="readonly"
:step="stepValue"
:use-grouping="useGrouping"
:class="cn(WidgetInputBaseClass, 'w-full text-xs')"
:pt="{
incrementButton: