Skip to content

Commit

Permalink
fix(Field.Currency): set limit of safe big number
Browse files Browse the repository at this point in the history
Fixes #3124
  • Loading branch information
tujoworker committed Jan 7, 2024
1 parent 2ca216a commit 323ad7c
Show file tree
Hide file tree
Showing 10 changed files with 157 additions and 102 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import DataValueReadwriteProperties from '../../data-value-readwrite-properties.
| `percent` | `boolean` | _(optional)_ Format a number as percentage. |
| `prefix` | `string` | _(optional)_ Text added before the value input. |
| `suffix` | `string` | _(optional)_ Text added after the value input. |
| `minimum` | `number` | _(optional)_ Validation for inclusive minimum number value (greater than or equal). |
| `maximum` | `number` | _(optional)_ Validation for inclusive maximum number value (less than or equal). |
| `minimum` | `number` | _(optional)_ Validation for inclusive minimum number value (greater than or equal). Defaults to `-Number.MAX_SAFE_INTEGER`. |
| `maximum` | `number` | _(optional)_ Validation for inclusive maximum number value (less than or equal). Defaults to `Number.MAX_SAFE_INTEGER`. |
| `exclusiveMinimum` | `number` | _(optional)_ Validation for exclusive minimum number value (greater than). |
| `exclusiveMaximum` | `number` | _(optional)_ Validation for exclusive maximum number value (less than). |
| `multipleOf` | `number` | _(optional)_ Validation that requires the number to be a multiple of given value. |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,25 +173,12 @@ The transformers are hooks to transform the value on different stages.

They should return a transformed value: `(value) => value`

- `toInput` transforms the value before it gets returned by the hook:
- `toInput` transforms the value before it gets returned as the `value`:

```ts
const { value } = useDataValue(props)
```
- `fromInput` transforms the value given by `handleChange` before it is used in the further process flow. Use it to destruct the value form the original event object.

- `fromInput` transforms the value given by `handleChange` before it is used in the further process flow.

```ts
handleChange(value)
```

- `toEvent` transforms the internal value before it gets returned by even callbacks such as `onChange`, `onFocus` and `onBlur`.
- `toEvent` transforms the internal value before it gets returned by even callbacks such as `onChange`, `onFocus` and `onBlur`. The second parameter returns the event type: `onChange`, `onFocus`, `onBlur` or `onBlurValidator`.

- `fromExternal` transforms the given props `value` before any other step gets entered.

#### Additional features

| Property | Type | Description |
| ------------ | --------- | -------------------------------------------------------------------------------------------------------------------------- |
| `capitalize` | `boolean` | _(optional)_ When set to `true`, it will capitalize the first letter of every word, transforming the rest to lowercase. |
| `trim` | `boolean` | _(optional)_ When `true`, it will trim leading and trailing whitespaces on blur, triggering onChange if the value changes. |
- `transformValue` transforms the value given by `handleChange` after `fromInput` and before `updateValue` and `toEvent`. The second parameter returns the current value
29 changes: 24 additions & 5 deletions packages/dnb-eufemia/src/extensions/forms/Field/Number/Number.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,14 +104,32 @@ function NumberComponent(props: Props) {
return external
}, [])
const fromInput = useCallback(
({ value, numberValue }: { value: string; numberValue: number }) => {
if (value === '') {
(event: { value?: string; numberValue: number }) => {
if (typeof event === 'number') {
event = { numberValue: event }
}

if (event?.value === '') {
return props.emptyValue
}
return numberValue

return event?.numberValue
},
[props.emptyValue]
)
const transformValue = useCallback(
(value: number, currentValue: number) => {
if (
value > Number.MAX_SAFE_INTEGER ||
value < -Number.MAX_SAFE_INTEGER
) {
return currentValue
}

return value
},
[]
)

const maskProps: Partial<InputMaskedProps> = useMemo(() => {
if (currency) {
Expand Down Expand Up @@ -155,6 +173,7 @@ function NumberComponent(props: Props) {
schema,
toInput,
fromInput,
transformValue,
size:
props.size !== 'small' && props.size !== 'large'
? 'medium'
Expand All @@ -174,8 +193,8 @@ function NumberComponent(props: Props) {
labelDescription,
labelSecondary,
value,
minimum,
maximum,
minimum = -Number.MAX_SAFE_INTEGER,
maximum = Number.MAX_SAFE_INTEGER,
disabled,
info,
warning,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,42 @@ describe('Field.Number', () => {
expect(screen.getByLabelText('Number label')).toBeInTheDocument()
})

it('corrects minimum number', () => {
render(<Field.Number value={-Number.MAX_SAFE_INTEGER} />)

const input = document.querySelector('input')

fireEvent.change(input, {
target: {
value: String(-Number.MAX_SAFE_INTEGER - 1),
},
})

expect(input).toHaveValue(String(-Number.MAX_SAFE_INTEGER - 1))

fireEvent.blur(input)

expect(input).toHaveValue(String(-Number.MAX_SAFE_INTEGER))
})

it('corrects maximum number', () => {
render(<Field.Number value={Number.MAX_SAFE_INTEGER} />)

const input = document.querySelector('input')

fireEvent.change(input, {
target: {
value: String(Number.MAX_SAFE_INTEGER + 1),
},
})

expect(input).toHaveValue(String(Number.MAX_SAFE_INTEGER + 1))

fireEvent.blur(input)

expect(input).toHaveValue(String(Number.MAX_SAFE_INTEGER))
})

it('should support disabled prop', () => {
const { rerender } = render(
<Field.Number label="Disabled label" disabled />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import FieldBlock from '../../FieldBlock'
import { useDataValue } from '../../hooks'
import { FieldProps, FieldHelpProps } from '../../types'
import { pickSpacingProps } from '../../../../components/flex/utils'
import { toCapitalized } from '../../../../shared/component-helper'

interface ErrorMessages {
required?: string
Expand Down Expand Up @@ -79,12 +80,40 @@ function StringComponent(props: Props) {
},
[props.emptyValue]
)
const toEvent = useCallback(
(value: string, type: string) => {
if (props.trim && type === 'onBlur') {
const spaces = '[\\s ]'
if (new RegExp(`^${spaces}|${spaces}$`).test(value)) {
value = value.replace(
new RegExp(`^${spaces}+|${spaces}+$`, 'g'),
''
)
handleChange(value)
}
}
return value
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[props.trim]
)
const transformValue = useCallback(
(value: string) => {
if (props.capitalize) {
value = toCapitalized(String(value || ''))
}
return value
},
[props.capitalize]
)

const preparedProps: Props = {
...props,
errorMessages,
schema,
fromInput,
toEvent,
transformValue,
width: props.width ?? 'large',
}

Expand Down Expand Up @@ -123,6 +152,11 @@ function StringComponent(props: Props) {
handleChange,
} = useDataValue(preparedProps)

const transformInstantly = useCallback(
(value: string) => (props.capitalize ? toCapitalized(value) : value),
[props.capitalize]
)

const characterCounterElement = characterCounter
? props.maxLength
? `${value?.length ?? '0'}/${props.maxLength}`
Expand All @@ -147,7 +181,7 @@ function StringComponent(props: Props) {
stretch: width !== undefined,
inner_ref: innerRef,
status: error || hasError ? 'error' : undefined,
value: value?.toString() ?? '',
value: transformInstantly(value?.toString() ?? ''),
}

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ describe('Field.String', () => {

const input = document.querySelector('input')

expect(input).toHaveValue('First Word')

await userEvent.type(input, ' second')
expect(input).toHaveValue('First Word Second')

Expand All @@ -112,19 +114,19 @@ describe('Field.String', () => {
render(
<Field.String
trim
value=" first"
value=" first"
onChange={onChange}
onBlur={onBlur}
/>
)

const input = document.querySelector('input')

expect(input).toHaveValue(' first')
expect(input).toHaveValue(' first')

await userEvent.type(input, ' second ')

expect(onChange).toHaveBeenLastCalledWith(' first second ')
expect(onChange).toHaveBeenLastCalledWith(' first second ')

fireEvent.blur(input)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,7 @@ describe('useDataValue', () => {
})

expect(toEvent).toHaveBeenCalledTimes(3)
expect(toEvent).toHaveBeenLastCalledWith(2)
expect(toEvent).toHaveBeenLastCalledWith(2, 'onBlur')

expect(onChange).toHaveBeenCalledTimes(1)
expect(onChange).toHaveBeenLastCalledWith(3)
Expand All @@ -485,7 +485,7 @@ describe('useDataValue', () => {
})

expect(toEvent).toHaveBeenCalledTimes(6)
expect(toEvent).toHaveBeenLastCalledWith(4)
expect(toEvent).toHaveBeenLastCalledWith(4, 'onBlur')

expect(onChange).toHaveBeenCalledTimes(2)
expect(onChange).toHaveBeenLastCalledWith(5)
Expand Down Expand Up @@ -546,54 +546,38 @@ describe('useDataValue', () => {

expect(onChange).toHaveBeenCalledTimes(1)
})
})

describe('manipulate string value', () => {
it('should capitalize value', () => {
const onBlur = jest.fn()
const onChange = jest.fn()
it('should call "transformValue"', () => {
const transformValue = jest.fn((v) => v + 1)

const { result } = renderHook(() =>
useDataValue({
value: 'foo',
capitalize: true,
onBlur,
onChange,
value: 1,
transformValue,
})
)

const { handleBlur, handleChange } = result.current
const { handleFocus, handleBlur, handleChange } = result.current

expect(transformValue).toHaveBeenCalledTimes(0)

act(() => {
handleFocus()
handleChange(2)
handleBlur()
handleChange('bar')
})

expect(onBlur).toHaveBeenLastCalledWith('Foo')
expect(onChange).toHaveBeenLastCalledWith('Bar')
})

it('should trim value', () => {
const onBlur = jest.fn()
const onChange = jest.fn()

const { result } = renderHook(() =>
useDataValue({
value: ' foo',
trim: true,
onBlur,
onChange,
})
)

const { handleBlur } = result.current
expect(transformValue).toHaveBeenCalledTimes(1)
expect(transformValue).toHaveBeenLastCalledWith(2, 1)

act(() => {
handleFocus()
handleChange(4)
handleBlur()
})

expect(onBlur).toHaveBeenLastCalledWith('foo')
expect(onChange).toHaveBeenLastCalledWith('foo')
expect(transformValue).toHaveBeenCalledTimes(2)
expect(transformValue).toHaveBeenLastCalledWith(4, 3)
})
})

Expand Down
Loading

0 comments on commit 323ad7c

Please sign in to comment.