Skip to content

Commit

Permalink
feat: Allow dynamic value change for spinbox #1154 (#2093)
Browse files Browse the repository at this point in the history
  • Loading branch information
marek-mihok authored Aug 3, 2023
1 parent 7d5aef3 commit e392e24
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 72 deletions.
160 changes: 92 additions & 68 deletions ui/src/spinbox.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,29 @@ describe('Spinbox.tsx', () => {
expect(wave.args[name]).toBe(0)
})

it('Set args when value is updated to different value', () => {
const { rerender } = render(<XSpinbox model={spinboxProps} />)
expect(wave.args[name]).toBe(0)
rerender(<XSpinbox model={{ ...spinboxProps, value: 40 }} />)
expect(wave.args[name]).toBe(40)
})

it('Set args when value is updated to initial value', () => {
const { getByTestId, rerender } = render(<XSpinbox model={{ ...spinboxProps, value: 40 }} />)
expect(wave.args[name]).toBe(40)
fireEvent.input(getByTestId(name), { target: { value: 50 } })
expect(wave.args[name]).toBe(50)
rerender(<XSpinbox model={{ ...spinboxProps, value: 40 }} />)
expect(wave.args[name]).toBe(40)
})

it('Changes out of bounds value when min/max is updated', () => {
const { rerender } = render(<XSpinbox model={{ ...spinboxProps, value: 40, min: 20, max: 50 }} />)
expect(wave.args[name]).toBe(40)
rerender(<XSpinbox model={{ ...spinboxProps, value: 40, min: 20, max: 30 }} />)
expect(wave.args[name]).toBe(30)
})

it('Sets args - init - min specified', () => {
render(<XSpinbox model={{ ...spinboxProps, min: 1 }} />)
expect(wave.args[name]).toBe(1)
Expand All @@ -57,23 +80,75 @@ describe('Spinbox.tsx', () => {
expect(wave.args[name]).toBe(100)
})

it('Sets args on increment', () => {
const { container } = render(<XSpinbox model={spinboxProps} />)

it('Should allow typing -', () => {
const
{ container } = render(<XSpinbox model={{ ...spinboxProps, min: -10 }} />),
spinboxInput = container.querySelector('.ms-spinButton-input') as HTMLInputElement

expect(spinboxInput).toHaveValue('0')
expect(wave.args[name]).toBe(0)
fireEvent.input(spinboxInput, { target: { value: '-' } })
expect(spinboxInput).toHaveValue('-')
expect(wave.args[name]).toBe(0)
fireEvent.input(spinboxInput, { target: { value: '-5' } })
expect(spinboxInput).toHaveValue('-5')
expect(wave.args[name]).toBe(-5)
})

it('Should overwrite user input when beyond max', () => {
const
{ container } = render(<XSpinbox model={{ ...spinboxProps, max: 10 }} />),
spinboxInput = container.querySelector('.ms-spinButton-input') as HTMLInputElement

expect(spinboxInput).toHaveValue('0')
expect(wave.args[name]).toBe(0)
fireEvent.input(spinboxInput, { target: { value: '5' } })
expect(spinboxInput).toHaveValue('5')
expect(wave.args[name]).toBe(5)
fireEvent.input(spinboxInput, { target: { value: '15' } })
expect(spinboxInput).toHaveValue('10')
expect(wave.args[name]).toBe(10)
})

it('Should overwrite user input when beyond min', () => {
const
{ container } = render(<XSpinbox model={{ ...spinboxProps, min: 10, value: 15 }} />),
spinboxInput = container.querySelector('.ms-spinButton-input') as HTMLInputElement

expect(spinboxInput).toHaveValue('15')
expect(wave.args[name]).toBe(15)
fireEvent.input(spinboxInput, { target: { value: '11' } })
expect(spinboxInput).toHaveValue('11')
expect(wave.args[name]).toBe(11)
fireEvent.input(spinboxInput, { target: { value: '5' } })
expect(spinboxInput).toHaveValue('10')
expect(wave.args[name]).toBe(10)
})

it('Calls push on increment if trigger specified', () => {
const { container } = render(<XSpinbox model={{ ...spinboxProps, trigger: true }} />)
simulateClick(container.querySelector('.ms-UpButton') as HTMLButtonElement)
expect(wave.args[name]).toBe(1)
expect(pushMock).toHaveBeenCalled()
})

it('Sets args on increment - not beyond max', () => {
const { container } = render(<XSpinbox model={{ ...spinboxProps, value: 1, max: 1 }} />)
it('Calls push on decrement if trigger specified', () => {
const { container } = render(<XSpinbox model={{ ...spinboxProps, trigger: true }} />)
simulateClick(container.querySelector('.ms-DownButton') as HTMLButtonElement)
expect(pushMock).toHaveBeenCalledTimes(1)
})

it('Sets args on increment', () => {
const { container } = render(<XSpinbox model={spinboxProps} />)
simulateClick(container.querySelector('.ms-UpButton') as HTMLButtonElement)
expect(wave.args[name]).toBe(1)
})

it('Calls push on increment if trigger specified', () => {
const { container } = render(<XSpinbox model={{ ...spinboxProps, trigger: true }} />)
it('Sets args on increment - not beyond max', () => {
const { container } = render(<XSpinbox model={{ ...spinboxProps, value: 1, max: 1 }} />)
simulateClick(container.querySelector('.ms-UpButton') as HTMLButtonElement)
expect(wave.args[name]).toBe(1)
expect(pushMock).toHaveBeenCalled()
})

it('Sets args on decrement', () => {
Expand All @@ -88,10 +163,16 @@ describe('Spinbox.tsx', () => {
expect(wave.args[name]).toBe(1)
})

it('Calls push on decrement if trigger specified', () => {
const { container } = render(<XSpinbox model={{ ...spinboxProps, trigger: true }} />)
simulateClick(container.querySelector('.ms-DownButton') as HTMLButtonElement)
expect(pushMock).toHaveBeenCalledTimes(1)
it('Calls push on input if trigger specified', () => {
const
{ container, getByTestId } = render(<XSpinbox model={{ ...spinboxProps, trigger: true }} />),
spinboxInput = container.querySelector('.ms-spinButton-input') as HTMLInputElement

fireEvent.input(getByTestId(name), { target: { value: 50 } })

expect(spinboxInput.value).toBe('50') // Should be updated as user types - immediately.
expect(jest.getTimerCount()).toBe(1) // Not called immediately, but after specified timeout.
expect(pushMock).toHaveBeenCalled()
})

it('Sets args on input', () => {
Expand All @@ -115,18 +196,6 @@ describe('Spinbox.tsx', () => {
expect(wave.args[name]).toBe(1)
})

it('Calls push on input if trigger specified', () => {
const
{ container, getByTestId } = render(<XSpinbox model={{ ...spinboxProps, trigger: true }} />),
spinboxInput = container.querySelector('.ms-spinButton-input') as HTMLInputElement

fireEvent.input(getByTestId(name), { target: { value: 50 } })

expect(spinboxInput.value).toBe('50') // Should be updated as user types - immediately.
expect(jest.getTimerCount()).toBe(1) // Not called immediately, but after specified timeout.
expect(pushMock).toHaveBeenCalled()
})

it('No floating point imprecision in increment', () => {
const
{ container } = render(<XSpinbox model={{ ...spinboxProps, value: 0, step: 0.0001 }} />),
Expand Down Expand Up @@ -192,49 +261,4 @@ describe('Spinbox.tsx', () => {
fireEvent.input(spinboxInput, { target: { value: '0.00011' } })
expect(spinboxInput).toHaveValue('0.0001')
})

it('Should allow typing -', () => {
const
{ container } = render(<XSpinbox model={{ ...spinboxProps, min: -10 }} />),
spinboxInput = container.querySelector('.ms-spinButton-input') as HTMLInputElement

expect(spinboxInput).toHaveValue('0')
expect(wave.args[name]).toBe(0)
fireEvent.input(spinboxInput, { target: { value: '-' } })
expect(spinboxInput).toHaveValue('-')
expect(wave.args[name]).toBe(0)
fireEvent.input(spinboxInput, { target: { value: '-5' } })
expect(spinboxInput).toHaveValue('-5')
expect(wave.args[name]).toBe(-5)
})

it('Should overwrite user input when beyond max', () => {
const
{ container } = render(<XSpinbox model={{ ...spinboxProps, max: 10 }} />),
spinboxInput = container.querySelector('.ms-spinButton-input') as HTMLInputElement

expect(spinboxInput).toHaveValue('0')
expect(wave.args[name]).toBe(0)
fireEvent.input(spinboxInput, { target: { value: '5' } })
expect(spinboxInput).toHaveValue('5')
expect(wave.args[name]).toBe(5)
fireEvent.input(spinboxInput, { target: { value: '15' } })
expect(spinboxInput).toHaveValue('10')
expect(wave.args[name]).toBe(10)
})

it('Should overwrite user input when beyond min', () => {
const
{ container } = render(<XSpinbox model={{ ...spinboxProps, min: 10, value: 15 }} />),
spinboxInput = container.querySelector('.ms-spinButton-input') as HTMLInputElement

expect(spinboxInput).toHaveValue('15')
expect(wave.args[name]).toBe(15)
fireEvent.input(spinboxInput, { target: { value: '11' } })
expect(spinboxInput).toHaveValue('11')
expect(wave.args[name]).toBe(11)
fireEvent.input(spinboxInput, { target: { value: '5' } })
expect(spinboxInput).toHaveValue('10')
expect(wave.args[name]).toBe(10)
})
})
16 changes: 12 additions & 4 deletions ui/src/spinbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,9 @@ const
return -groups[1]?.length || groups[2]?.length || 0
}
export const
XSpinbox = ({ model: { name, trigger, label, disabled, min = 0, max = 100, step = 1, value = 0 } }: { model: Spinbox }) => {
XSpinbox = ({ model: m }: { model: Spinbox }) => {
const
{ name, trigger, label, disabled, min = 0, max = 100, step = 1, value = 0 } = m,
[val, setVal] = React.useState<S>(String(value)),
precision = Math.max(calculatePrecision(step), 0),
parseValue = React.useCallback((v: F) => {
Expand All @@ -78,13 +79,15 @@ export const
wave.args[name] = newValue
if (trigger) wave.push()
setVal(String(newValue))
m.value = newValue
return String(newValue)
},
onDecrement = () => {
const newValue = Math.max(parseValue(Number(val) - step), min)
wave.args[name] = newValue
if (trigger) wave.push()
setVal(String(newValue))
m.value = newValue
return String(newValue)
},
handleValue = React.useCallback((val: S) => {
Expand Down Expand Up @@ -112,12 +115,17 @@ export const
}, [name, trigger]),
debouncedHandleOnInput = React.useRef(wave.debounce(DEBOUNCE_TIMEOUT, handleOnInput)),
onInput = (e: React.SyntheticEvent<HTMLElement>) => {
const numVal = handleValue((e.target as HTMLInputElement).value)
const inputValue = (e.target as HTMLInputElement).value
const numVal = handleValue(inputValue)
if (String(numVal) === inputValue) m.value = numVal
trigger ? debouncedHandleOnInput.current(numVal) : handleOnInput(numVal)
}

// eslint-disable-next-line react-hooks/exhaustive-deps
React.useEffect(() => { wave.args[name] = (value < min) ? min : ((value > max) ? max : value) }, [])
React.useEffect(() => {
const val = (value < min) ? min : ((value > max) ? max : value)
wave.args[name] = val
setVal(String(val))
}, [max, min, name, value])

return (
<Fluent.SpinButton
Expand Down

0 comments on commit e392e24

Please sign in to comment.