Skip to content

Commit

Permalink
fix(Forms): fix schema validation for required paths with matching na…
Browse files Browse the repository at this point in the history
…me (#4189)

Fixes #4179
  • Loading branch information
tujoworker authored Oct 29, 2024
1 parent 16b6101 commit 04caf61
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -948,7 +948,7 @@ describe('Form.Section', () => {
).toHaveAttribute('aria-required', 'true')
})

it('should set "required" for firstName with nested schema', () => {
it('should set "required" in schema with section', () => {
const schema: JSONSchema = {
type: 'object',
properties: {
Expand Down Expand Up @@ -978,6 +978,96 @@ describe('Form.Section', () => {
).toHaveAttribute('aria-required', 'true')
})

it('should set "required" in schema with section in nested object', () => {
const schema: JSONSchema = {
type: 'object',
properties: {
myObject: {
type: 'object',
properties: {
mySection: {
type: 'object',
properties: {
firstName: {
type: 'string',
},
},
required: ['firstName'],
},
},
},
},
}

render(
<Form.Handler schema={schema}>
<MySection path="/myObject/mySection" />
</Form.Handler>
)

expect(
document.querySelector('input[name="firstName"]')
).toHaveAttribute('aria-required', 'true')
expect(
document.querySelector('input[name="lastName"]')
).toHaveAttribute('aria-required', 'true')
})

it('should set "required" in schema with section of the same name', () => {
const schema: JSONSchema = {
type: 'object',
properties: {
firstName: {
type: 'object',
properties: {
firstName: {
type: 'string',
},
},
required: ['firstName'],
},
},
}

render(
<Form.Handler schema={schema}>
<MySection path="/firstName" />
</Form.Handler>
)

expect(
document.querySelector('input[name="firstName"]')
).toHaveAttribute('aria-required', 'true')
expect(
document.querySelector('input[name="lastName"]')
).toHaveAttribute('aria-required', 'true')
})

it('should not set "required" for field path that matches a schema path', () => {
const schema: JSONSchema = {
type: 'object',
required: ['longPath_with_firstName_inside'],
properties: {
longPath_with_firstName_inside: {
type: 'string',
},
},
}

render(
<Form.Handler schema={schema}>
<MySection path="/firstName" />
</Form.Handler>
)

expect(
document.querySelector('input[name="firstName"]')
).not.toHaveAttribute('aria-required')
expect(
document.querySelector('input[name="lastName"]')
).toHaveAttribute('aria-required', 'true')
})

it('should overwrite minLength', () => {
const schema: JSONSchema = {
type: 'object',
Expand Down
80 changes: 51 additions & 29 deletions packages/dnb-eufemia/src/extensions/forms/hooks/useFieldProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,11 +227,12 @@ export default function useFieldProps<Value, EmptyValue, Props>(

const hasPath = Boolean(pathProp)
const hasItemPath = Boolean(itemPath)
const { path, identifier, makeIteratePath } = usePath({
id,
path: pathProp,
itemPath,
})
const { path, identifier, makeIteratePath, joinPath, cleanPath } =
usePath({
id,
path: pathProp,
itemPath,
})

const defaultValueRef = useRef(defaultValue)
useLayoutEffect(() => {
Expand Down Expand Up @@ -264,34 +265,55 @@ export default function useFieldProps<Value, EmptyValue, Props>(
return requiredProp
}

const paths = identifier.split('/')
if (paths.length > 0 && (schema || dataContext?.schema)) {
const requiredList = [schema?.['required']]

if (paths.length > 1) {
const schema = dataContext.schema
const schemaPath = paths.slice(0, -1).join('/properties/')
const schemaPart = pointer.has(schema, schemaPath)
? pointer.get(schema, schemaPath)
: schema

requiredList.push(schemaPart?.['required'])
}

if (sectionPath) {
paths.push(sectionPath.substring(1))
}
if (schema || dataContext?.schema) {
const paths = identifier.split('/')
if (paths.length > 0) {
const requiredInSchema = [schema?.['required']]

// - Handle context schema
if (paths.length > 1) {
const schema = dataContext.schema
const pathWithoutLast = paths.slice(0, -1).join('/properties/')
const schemaPart = pointer.has(schema, pathWithoutLast)
? pointer.get(schema, pathWithoutLast)
: schema

const requiredSchemaList = schemaPart?.['required']
if (Array.isArray(requiredSchemaList)) {
const rootPath = pathWithoutLast.replace(/properties\//g, '')
const requiredList = requiredSchemaList.map((path) => {
path = cleanPath('/' + path)
return sectionPath && path.startsWith(sectionPath)
? path
: joinPath([sectionPath || rootPath, path])
})
requiredInSchema.push(requiredList)
}
}

const collected = requiredList.flatMap((v) => v).filter(Boolean)
if (
paths
const collected = requiredInSchema
.flatMap((value) => value)
.filter(Boolean)
.some((p) => collected.some((c) => c.includes(p)))
) {
return true

if (
collected.filter(Boolean).some((path) => {
path = cleanPath('/' + path)
return identifier === path || sectionPath === path
})
) {
return true
}
}
}
}, [requiredProp, identifier, schema, dataContext.schema, sectionPath])
}, [
cleanPath,
dataContext.schema,
identifier,
joinPath,
requiredProp,
schema,
sectionPath,
])

// Error handling
// - Should errors received through validation be shown initially. Assume that providing a direct prop to
Expand Down
1 change: 1 addition & 0 deletions packages/dnb-eufemia/src/extensions/forms/hooks/usePath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ export default function usePath(props: Props = {}) {
makePath,
makeIteratePath,
makeSectionPath,
cleanPath,
}
}

Expand Down

0 comments on commit 04caf61

Please sign in to comment.