Skip to content

Commit

Permalink
fix(AnyOfControl): fix missing default combinator value bug (#75)
Browse files Browse the repository at this point in the history
  • Loading branch information
DrewHoo committed Apr 24, 2024
1 parent 8ce89ee commit 768d8c4
Show file tree
Hide file tree
Showing 14 changed files with 314 additions and 93 deletions.
8 changes: 4 additions & 4 deletions src/common/AntDJsonForm.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { JsonForms } from "@jsonforms/react"
import { JsonForms, JsonFormsReactProps } from "@jsonforms/react"
import {
JsonFormsRendererRegistryEntry,
JsonFormsUISchemaRegistryEntry,
Expand All @@ -12,7 +12,7 @@ import {

type Props<T> = {
data: Record<string, unknown>
updateData: (data: Record<string, unknown>) => void
onChange: Required<JsonFormsReactProps>["onChange"]
jsonSchema: T
uiSchema?: UISchema<T>
uiSchemaRegistryEntries?: JsonFormsUISchemaRegistryEntry[]
Expand All @@ -25,7 +25,7 @@ export function AntDJsonForm<T = Record<string, unknown>>({
uiSchema,
jsonSchema,
data,
updateData,
onChange,
uiSchemaRegistryEntries,
customRendererRegistryEntries,
rendererRegistryEntries = _rendererRegistryEntries,
Expand All @@ -37,7 +37,7 @@ export function AntDJsonForm<T = Record<string, unknown>>({
uischema={uiSchema}
uischemas={uiSchemaRegistryEntries ?? []}
data={data}
onChange={({ data }) => updateData(data as Record<string, unknown>)}
onChange={onChange}
cells={[...cellRegistryEntries]}
renderers={[
...rendererRegistryEntries,
Expand Down
50 changes: 35 additions & 15 deletions src/common/FormStateWrapper.tsx
Original file line number Diff line number Diff line change
@@ -1,48 +1,68 @@
import { JsonForms } from "@jsonforms/react"
import { Form } from "antd"
/* eslint-disable @typescript-eslint/no-misused-promises */
import { JsonForms, JsonFormsReactProps } from "@jsonforms/react"
import { Button, Form } from "antd"

import { JsonFormsUISchemaRegistryEntry, JsonSchema7 } from "@jsonforms/core"
import { UISchema } from "../ui-schema"
import {
cellRegistryEntries,
rendererRegistryEntries,
} from "../renderer-registry-entries"
import { useState } from "react"
import { useCallback, useState } from "react"

export type RenderProps<T extends Record<string, unknown>, S> = {
schema: S
data?: T
uischema?: UISchema<S>
onChange?: (result: { data: T }) => void
onChange?: JsonFormsReactProps["onChange"]
uiSchemaRegistryEntries?: JsonFormsUISchemaRegistryEntry[]
}

export function FormStateWrapper<T extends Record<string, unknown>, S>({
schema,
uischema,
data: initialData,
onChange,
onChange: _onChange,
uiSchemaRegistryEntries,
}: RenderProps<T, S>) {
const [data, setData] = useState<Record<string, unknown> | undefined>(
initialData,
const [result, setResult] = useState({
data: initialData,
})
const onChange: Required<JsonFormsReactProps>["onChange"] = useCallback(
(r) => {
_onChange?.(r)
setResult(r)
},
[_onChange],
)
const [form] = Form.useForm()
const onSubmit = useCallback(async () => {
const formValidationResult = await form
.validateFields()
.then((values: Record<string, unknown>) => values)
.catch((errorInfo: { errorFields: unknown[] }) => errorInfo)

if ("errorFields" in formValidationResult) {
return // nothing to do; validateFields will have already rendered error messages on form fields
}
}, [form])

return (
<Form>
<Form form={form}>
<JsonForms
schema={schema as JsonSchema7}
uischema={uischema}
renderers={rendererRegistryEntries}
cells={cellRegistryEntries}
data={data}
data={result?.data}
uischemas={uiSchemaRegistryEntries ?? []}
{...(onChange
? { onChange }
: {
onChange: (result) =>
setData(result.data as Record<string, unknown>),
})}
onChange={onChange}
/>
<Form.Item>
<Button type="primary" onClick={onSubmit}>
Submit
</Button>
</Form.Item>
</Form>
)
}
58 changes: 41 additions & 17 deletions src/common/StorybookAntDJsonForm.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
/* eslint-disable @typescript-eslint/no-misused-promises */
import {
JsonFormsRendererRegistryEntry,
JsonFormsUISchemaRegistryEntry,
} from "@jsonforms/core"
import { UISchema } from "../ui-schema"
import { AntDJsonForm } from "./AntDJsonForm"
import { useState } from "react"
import { useCallback, useState } from "react"
import { Button, Form } from "antd"
import { JsonFormsReactProps } from "@jsonforms/react"

type Props<T> = {
data?: Record<string, unknown>
Expand All @@ -13,7 +16,7 @@ type Props<T> = {
uiSchema?: UISchema<T>
uiSchemaRegistryEntries?: JsonFormsUISchemaRegistryEntry[]
config?: Record<string, unknown>
onChange: (data: Record<string, unknown>) => void
onChange: JsonFormsReactProps["onChange"]
}

// this component exists to facilitate storybook rendering
Expand All @@ -24,22 +27,43 @@ export function StorybookAntDJsonForm<T>({
uiSchemaRegistryEntries,
rendererRegistryEntries,
config,
onChange,
onChange: _onChange,
}: Props<T>) {
const [data, setData] = useState(initialData)
const updateData = (newData: Record<string, unknown>) => {
setData(newData)
onChange(newData)
}
const [result, setResult] = useState({ data: initialData })
const onChange = useCallback(
(r: { data: Record<string, unknown>; errors: [] }) => {
setResult(r)
_onChange?.(r)
},
[_onChange],
)
const [form] = Form.useForm()
const onSubmit = useCallback(async () => {
const formValidationResult = await form
.validateFields()
.then((values: Record<string, unknown>) => values)
.catch((errorInfo: { errorFields: unknown[] }) => errorInfo)

if ("errorFields" in formValidationResult) {
return // nothing to do; validateFields will have already rendered error messages on form fields
}
}, [form])
return (
<AntDJsonForm<typeof jsonSchema>
uiSchema={uiSchema}
jsonSchema={jsonSchema}
data={data}
updateData={(newData) => updateData(newData)}
uiSchemaRegistryEntries={uiSchemaRegistryEntries}
rendererRegistryEntries={rendererRegistryEntries}
config={config}
/>
<Form form={form}>
<AntDJsonForm<typeof jsonSchema>
uiSchema={uiSchema}
jsonSchema={jsonSchema}
data={result.data}
onChange={onChange}
uiSchemaRegistryEntries={uiSchemaRegistryEntries}
rendererRegistryEntries={rendererRegistryEntries}
config={config}
/>
<Form.Item>
<Button type="primary" onClick={onSubmit}>
Submit
</Button>
</Form.Item>
</Form>
)
}
21 changes: 15 additions & 6 deletions src/controls/NumericControl.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,32 +14,41 @@ import {
numericPriceSchema,
numericUSDUISchema,
} from "../testSchemas/numericSchema"
import { JSONFormData } from "../common/schema-derived-types"

describe("NumericControl", () => {
it("does not fall back to default if value is empty", () => {
it("does not fall back to default if value is provided", () => {
const data: JSONFormData<typeof numericTheNumberSchema> = {
numericValue: 0.999,
}

render({
schema: numericTheNumberSchema,
data: {},
data,
})

expect(screen.getByRole("spinbutton")).toHaveValue("")
expect(screen.getByRole("spinbutton")).toHaveValue("0.999")
})

it("calls onChange with number values", async () => {
let data = { numericValue: 42.0 }
let data: JSONFormData<typeof numericTheNumberSchema> = {
numericValue: 42.0,
}
render({
schema: numericTheNumberSchema,
data,
onChange: (state) => {
data = state.data
data = state.data as JSONFormData<typeof numericTheNumberSchema>
},
})

await userEvent.clear(screen.getByRole("spinbutton"))
await userEvent.type(screen.getByRole("spinbutton"), "42.00")

await waitFor(() => {
expect(data).toBe(42.0)
expect(data).toEqual({
numericValue: 42.0,
})
})
})

Expand Down
9 changes: 6 additions & 3 deletions src/controls/NumericSliderControl.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
numericSliderPercentageUISchema,
numericSliderUISchemaWithRule,
} from "../testSchemas/numericSliderSchema"
import { JSONFormData } from "../common/schema-derived-types"

describe("NumericSliderControl", () => {
test("renders a slider and number input with no UISchema provided", () => {
Expand Down Expand Up @@ -67,12 +68,14 @@ describe("NumericSliderControl", () => {
})

it("calls onChange with number values", async () => {
let data = { numericRangeValue: 42.0 }
let data: JSONFormData<typeof numericSliderBasisPointsSchema> = {
numericRangeValue: 42.0,
}
render({
schema: numericSliderBasisPointsSchema,
data,
onChange: (state) => {
data = state.data
data = state.data as JSONFormData<typeof numericSliderBasisPointsSchema>
},
})

Expand All @@ -93,6 +96,6 @@ describe("NumericSliderControl", () => {
expect(screen.queryByText("50%")).toBeNull()
await userEvent.hover(slider)

screen.getByText("50%")
await screen.findByText("50%")
})
})
17 changes: 9 additions & 8 deletions src/controls/ObjectArrayControl.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
objectArrayWithCombinator_FavoriteThing1UISchemaRegistryEntry as objectArrayWithCombinator_CombinatorSubschemaUISchemaRegistryEntry,
} from "../testSchemas/arraySchema"
import { UISchema } from "../ui-schema"
import { JSONFormData } from "../common/schema-derived-types"

describe("ObjectArrayControl", () => {
test.each([
Expand Down Expand Up @@ -60,14 +61,14 @@ describe("ObjectArrayControl", () => {
)

test("correctly appends to the list with add button", async () => {
let data = { assets: [] }
let data: JSONFormData<typeof objectArrayControlJsonSchema> = { assets: [] }
const user = userEvent.setup()
render({
schema: objectArrayControlJsonSchema,
uischema: arrayControlUISchema,
data: data,
data,
onChange: (result) => {
data = result.data
data = result.data as JSONFormData<typeof objectArrayControlJsonSchema>
},
})

Expand All @@ -82,7 +83,7 @@ describe("ObjectArrayControl", () => {
})

test("correctly removes from the list with remove button", async () => {
let data = {
let data: JSONFormData<typeof objectArrayControlJsonSchema> = {
assets: [
{ asset: "my asset" },
{ asset: "remove me!" },
Expand All @@ -95,7 +96,7 @@ describe("ObjectArrayControl", () => {
uischema: arrayControlUISchema,
data: data,
onChange: (result) => {
data = result.data
data = result.data as JSONFormData<typeof objectArrayControlJsonSchema>
},
})
await screen.findByDisplayValue("my asset")
Expand All @@ -121,13 +122,13 @@ describe("ObjectArrayControl", () => {

test("renders with overwritten icons and does not allow overwriting onClick", async () => {
const user = userEvent.setup()
let data = { assets: [] }
let data: JSONFormData<typeof objectArrayControlJsonSchema> = { assets: [] }
render({
schema: objectArrayControlJsonSchema,
uischema: arrayControlUISchemaWithIcons,
data: data,
data,
onChange: (result) => {
data = result.data
data = result.data as JSONFormData<typeof objectArrayControlJsonSchema>
},
})
// Add button text is overwritten and has the correct icon
Expand Down
8 changes: 4 additions & 4 deletions src/controls/PrimitiveArrayControl.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
arrayInsideCombinatorSchema,
} from "../testSchemas/arraySchema"
import { UISchema } from "../ui-schema"
import { JSONFormData } from "../common/schema-derived-types"

describe("PrimitiveArrayControl", () => {
test("renders without any data", async () => {
Expand All @@ -20,8 +21,7 @@ describe("PrimitiveArrayControl", () => {
uischema: arrayControlUISchema,
})
await screen.findByPlaceholderText("Enter value")
screen.getByRole("button")
screen.getByText("Add Assets")
screen.getByRole("button", { name: "Add Assets" })
})

test.each([
Expand Down Expand Up @@ -65,14 +65,14 @@ describe("PrimitiveArrayControl", () => {
)

test("correctly appends to the list with add button", async () => {
let data = {}
let data: JSONFormData<typeof stringArrayControlJsonSchema> = {}
const user = userEvent.setup()
render({
schema: stringArrayControlJsonSchema,
uischema: arrayControlUISchema,
data: data,
onChange: (result) => {
data = result.data
data = result.data as JSONFormData<typeof stringArrayControlJsonSchema>
},
})
const newAsset = await screen.findByPlaceholderText("Enter value")
Expand Down
2 changes: 1 addition & 1 deletion src/controls/TextControl.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ test("updates jsonforms data as expected", async () => {
schema: textInputSchema,
data,
onChange: (result) => {
data = result.data
data = result.data as JSONFormData<typeof textInputSchema>
},
})

Expand Down
Loading

0 comments on commit 768d8c4

Please sign in to comment.