Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(Forms): ensure emptyValue is set in the data context when defined #4111

Merged
merged 1 commit into from
Oct 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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 @@ -55,15 +55,15 @@ describe('ArraySelection', () => {
it('handles emptyValue correctly', () => {
const handleChange = jest.fn()
render(
<Field.ArraySelection onChange={handleChange} emptyValue="empty">
<Field.ArraySelection onChange={handleChange} emptyValue={[]}>
<Field.Option value="option1">Option 1</Field.Option>
<Field.Option value="option2">Option 2</Field.Option>
</Field.ArraySelection>
)

fireEvent.click(screen.getByText('Option 1'))
fireEvent.click(screen.getByText('Option 1'))
expect(handleChange).toHaveBeenLastCalledWith('empty')
expect(handleChange).toHaveBeenLastCalledWith([])
})

it('displays error message when error prop is provided', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -844,4 +844,45 @@ describe('Field.Number', () => {
expect(input).toHaveAttribute('aria-invalid', 'true')
})
})

describe('emptyValue', () => {
it('should use the given emptyValue and set in the data context', async () => {
const onSubmit = jest.fn()

render(
<Form.Handler onSubmit={onSubmit}>
<Field.Number path="/myValue" emptyValue={0} />
</Form.Handler>
)

const form = document.querySelector('form')
const input = document.querySelector('input')
expect(input).toHaveValue('0')

fireEvent.submit(form)
expect(onSubmit).toHaveBeenCalledTimes(1)
expect(onSubmit).toHaveBeenLastCalledWith(
{ myValue: 0 },
expect.anything()
)

await userEvent.type(input, '1')

fireEvent.submit(form)
expect(onSubmit).toHaveBeenCalledTimes(2)
expect(onSubmit).toHaveBeenLastCalledWith(
{ myValue: 1 },
expect.anything()
)

await userEvent.type(input, '{Backspace}')

fireEvent.submit(form)
expect(onSubmit).toHaveBeenCalledTimes(3)
expect(onSubmit).toHaveBeenLastCalledWith(
{ myValue: 0 },
expect.anything()
)
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -1529,4 +1529,84 @@ describe('Field.String', () => {
expect(third).toHaveTextContent(inputInfo)
})
})

describe('emptyValue', () => {
it('should use the given emptyValue and set in the data context', async () => {
const onSubmit = jest.fn()

render(
<Form.Handler onSubmit={onSubmit}>
<Field.String path="/myValue" emptyValue="" />
</Form.Handler>
)

const form = document.querySelector('form')
const input = document.querySelector('input')
expect(input).toHaveValue('')

fireEvent.submit(form)
expect(onSubmit).toHaveBeenCalledTimes(1)
expect(onSubmit).toHaveBeenLastCalledWith(
{ myValue: '' },
expect.anything()
)

await userEvent.type(input, ' ')

fireEvent.submit(form)
expect(onSubmit).toHaveBeenCalledTimes(2)
expect(onSubmit).toHaveBeenLastCalledWith(
{ myValue: ' ' },
expect.anything()
)

await userEvent.type(input, '{Backspace}')

fireEvent.submit(form)
expect(onSubmit).toHaveBeenCalledTimes(3)
expect(onSubmit).toHaveBeenLastCalledWith(
{ myValue: '' },
expect.anything()
)
})

it('should set the emptyValue when string gets empty', async () => {
const onSubmit = jest.fn()

render(
<Form.Handler onSubmit={onSubmit}>
<Field.String path="/myValue" emptyValue="foo" />
</Form.Handler>
)

const form = document.querySelector('form')
const input = document.querySelector('input')
expect(input).toHaveValue('foo')

fireEvent.submit(form)
expect(onSubmit).toHaveBeenCalledTimes(1)
expect(onSubmit).toHaveBeenLastCalledWith(
{ myValue: 'foo' },
expect.anything()
)

await userEvent.type(input, ' ')

fireEvent.submit(form)
expect(onSubmit).toHaveBeenCalledTimes(2)
expect(onSubmit).toHaveBeenLastCalledWith(
{ myValue: 'foo ' },
expect.anything()
)

await userEvent.type(input, '{Backspace>4}')

fireEvent.submit(form)
expect(onSubmit).toHaveBeenCalledTimes(3)
expect(onSubmit).toHaveBeenLastCalledWith(
{ myValue: 'foo' },
expect.anything()
)
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -2938,6 +2938,54 @@ describe('useFieldProps', () => {
expect(result.current.error).toBeInstanceOf(Error)
})

it('should set emptyValue when handleChange gets undefined', async () => {
const onSubmit = jest.fn(() => null)

const first = {}
const { result } = renderHook(useFieldProps, {
initialProps: {
path: '/foo',
emptyValue: first,
},
wrapper: (props) => <Form.Handler {...props} onSubmit={onSubmit} />,
})

const form = document.querySelector('form')

fireEvent.submit(form)
expect(onSubmit).toHaveBeenCalledTimes(1)
expect(onSubmit).toHaveBeenLastCalledWith(
{ foo: first },
expect.anything()
)
expect(result.current.value).toBe(first)

const second = {}
act(() => {
result.current.handleChange(second)
})

fireEvent.submit(form)
expect(onSubmit).toHaveBeenCalledTimes(2)
expect(onSubmit).toHaveBeenLastCalledWith(
{ foo: second },
expect.anything()
)
expect(result.current.value).toBe(second)

act(() => {
result.current.handleChange(undefined)
})

fireEvent.submit(form)
expect(onSubmit).toHaveBeenCalledTimes(3)
expect(onSubmit).toHaveBeenLastCalledWith(
{ foo: first },
expect.anything()
)
expect(result.current.value).toBe(first)
})

it('should call async context onChange regardless of error when executeOnChangeRegardlessOfError is true', async () => {
const onChange = jest.fn(async () => null)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,27 +30,29 @@ export default function useExternalValue<Value>(props: Props<Value>) {
return useMemo(() => {
if (value !== emptyValue) {
// Value-prop sent directly to the field has highest priority, overriding any surrounding source
return transformers?.current?.fromExternal?.(value)
return transformers?.current?.fromExternal?.(value) ?? emptyValue
}

if (inIterate && itemPath) {
// This field is inside an iterate, and has a pointer from the base of the element being iterated
if (itemPath === '/') {
return iterateElementValue
return iterateElementValue ?? emptyValue
}

return pointer.has(iterateElementValue, itemPath)
? pointer.get(iterateElementValue, itemPath)
: emptyValue
if (pointer.has(iterateElementValue, itemPath)) {
return pointer.get(iterateElementValue, itemPath) ?? emptyValue
}
}

if (data && path) {
// There is a surrounding data context and a path for where in the source to find the data
if (path === '/') {
return data
return data ?? emptyValue
}

return pointer.has(data, path) ? pointer.get(data, path) : emptyValue
if (pointer.has(data, path)) {
return pointer.get(data, path) ?? emptyValue
}
}

return emptyValue
Expand Down
15 changes: 6 additions & 9 deletions packages/dnb-eufemia/src/extensions/forms/hooks/useFieldProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1348,10 +1348,9 @@ export default function useFieldProps<Value, EmptyValue, Props>(
return
}

const transformedValue = transformers.current.transformValue(
newValue,
currentValue
)
const transformedValue =
transformers.current.transformValue(newValue, currentValue) ??
(emptyValue as unknown as Value)
const contextValue = transformers.current.transformOut(
transformedValue,
transformers.current.provideAdditionalArgs(
Expand Down Expand Up @@ -1390,6 +1389,7 @@ export default function useFieldProps<Value, EmptyValue, Props>(
})
},
[
emptyValue,
additionalArgs,
hasPath,
itemPath,
Expand Down Expand Up @@ -1639,7 +1639,7 @@ export default function useFieldProps<Value, EmptyValue, Props>(
return // stop here
}

let valueToStore: Value | unknown = valueProp
let valueToStore: Value | unknown = valueProp ?? emptyValue

const data = wizardContext?.prerenderFieldProps
? dataContext.data
Expand Down Expand Up @@ -1777,6 +1777,7 @@ export default function useFieldProps<Value, EmptyValue, Props>(
dataContext.data,
dataContext.id,
dataContext.internalDataRef,
emptyValue,
hasItemPath,
hasPath,
identifier,
Expand Down Expand Up @@ -2040,10 +2041,6 @@ export default function useFieldProps<Value, EmptyValue, Props>(

/** Documented APIs */
id,
// value: valueRef.current,
// value: transformers.current.transformIn(
// transformers.current.toInput(valueRef.current)
// ),
value: transformers.current.toInput(valueRef.current),
hasError: hasVisibleError,
isChanged: Boolean(changedRef.current),
Expand Down
Loading