Skip to content

Commit

Permalink
Updated based on comments, plus respect requires
Browse files Browse the repository at this point in the history
  • Loading branch information
TrangPham committed Mar 19, 2024
1 parent a8c7d20 commit 368b1e6
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 91 deletions.
95 changes: 55 additions & 40 deletions src/controls/ObjectArrayControl.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { test } from "vitest"
import { test, expect } from "vitest"
import { screen, waitFor } from "@testing-library/react"
import userEvent from "@testing-library/user-event"
import { render } from "../common/test-render"
Expand All @@ -7,8 +7,8 @@ import {
objectArrayControlUISchema,
objectArrayControlJsonSchema,
objectArrayControlUISchemaWithIcons,
objectArrayControlJsonSchemaWithRequired,
} from "../testSchemas/objectArraySchema"
import { expect } from "@storybook/test"

test("ObjectArrayControl renders without any data", async () => {
render({
Expand All @@ -20,32 +20,45 @@ test("ObjectArrayControl renders without any data", async () => {
await screen.findByText("No data")
})

test("ObjectArrayControl renders disabled remove button with one element", async () => {
render({
schema: objectArrayControlJsonSchema,
uischema: objectArrayControlUISchema,
data: { assets: [{ asset: "my asset" }] },
})
await screen.findByText("Add Assets")
await screen.findByDisplayValue("my asset")
//note: the text is within a span in the <button>
const removeButton = (await screen.findByText("Delete")).parentNode
expect(removeButton).toHaveProperty("disabled", true)
})
test.each([
[objectArrayControlJsonSchema, false],
[objectArrayControlJsonSchemaWithRequired, true],
])(
"ObjectArrayControl renders disabled remove button with one element if required",
async (schema, should_be_disabled) => {
render({
schema: schema,
uischema: objectArrayControlUISchema,
data: { assets: [{ asset: "my asset" }] },
})
await screen.findByText("Add Assets")
await screen.findByDisplayValue("my asset")
//note: the text is within a span in the <button>
const removeButton = await screen.findByRole("button", { name: "Delete" })
expect(removeButton).toHaveProperty("disabled", should_be_disabled)
},
)

test("ObjectArrayControl renders enabled remove buttons with multiple elements", async () => {
render({
schema: objectArrayControlJsonSchema,
uischema: objectArrayControlUISchema,
data: { assets: [{ asset: "my asset" }, { asset: "my other asset" }] },
})
await screen.findByText("Add Assets")
await screen.findByDisplayValue("my asset")
await screen.findByDisplayValue("my other asset")
//note: the text is within a span in the <button>
const removeButton = (await screen.findAllByText("Delete"))[0].parentNode
expect(removeButton).toHaveProperty("disabled", false)
})
// test with paramaterized schema
test.each([
[objectArrayControlJsonSchema],
[objectArrayControlJsonSchemaWithRequired],
])(
"ObjectArrayControl renders enabled remove buttons with multiple elements",
async (schema) => {
render({
schema: schema,
uischema: objectArrayControlUISchema,
data: { assets: [{ asset: "my asset" }, { asset: "my other asset" }] },
})
await screen.findByText("Add Assets")
await screen.findByDisplayValue("my asset")
await screen.findByDisplayValue("my other asset")
//note: the text is within a span in the <button>
const removeButton = (await screen.findAllByText("Delete"))[0].parentNode
expect(removeButton).toHaveProperty("disabled", false)
},
)

test("ObjectArrayControl correctly appends to the list with add button", async () => {
let data = { assets: [{ asset: "my asset" }] }
Expand All @@ -61,11 +74,11 @@ test("ObjectArrayControl correctly appends to the list with add button", async (
await screen.findByDisplayValue("my asset")
await user.click(screen.getByRole("button", { name: "Add Assets" }))
const newAsset = await screen.findByDisplayValue("")
await user.type(newAsset, "my other asset")
await screen.findByDisplayValue("my other asset")
await user.type(newAsset, "new")
await screen.findByDisplayValue("new")
await waitFor(() => {
expect(data).toEqual({
assets: [{ asset: "my asset" }, { asset: "my other asset" }],
assets: [{ asset: "my asset" }, { asset: "new" }],
})
})
})
Expand Down Expand Up @@ -102,11 +115,13 @@ test("ObjectArrayControl correctly removes from the list with remove button", as
name: "Delete",
})
expect(updatedRemoveButtons).toHaveLength(2)
await screen.findByDisplayValue("my asset")
await screen.findByDisplayValue("my other asset")
})

test("ObjectArrayControl renders with overwritten icons and does not allow overwriting onClick", async () => {
const user = userEvent.setup()
let data = { assets: [{ asset: "my asset" }, { asset: "my other asset" }] }
let data = { assets: [] }
render({
schema: objectArrayControlJsonSchema,
uischema: objectArrayControlUISchemaWithIcons,
Expand All @@ -118,26 +133,26 @@ test("ObjectArrayControl renders with overwritten icons and does not allow overw
// Add button text is overwritten and has the correct icon
await screen.findByText("Add more items")
await screen.findByLabelText("plus-circle") // fyi: aria-label is "plus-circle"
// Delete button text is overwritten and has the correct icon
expect(await screen.findAllByText("Destroy me!")).toHaveLength(2)
expect(await screen.findAllByLabelText("delete")).toHaveLength(2) // fyi: aria-label is "delete"

// Check that the onClick handler is not overwritten on Add button
user.click(await screen.findByText("Add more items"))
await user.click(await screen.findByText("Add more items"))
await screen.findByDisplayValue("")
await waitFor(() => {
expect(data).toEqual({
assets: [{ asset: "my asset" }, { asset: "my other asset" }, {}],
assets: [{}],
})
})

// Delete button text is overwritten and has the correct icon
await screen.findByText("Destroy me!")
await screen.findByLabelText("delete") // fyi: aria-label is "delete"

// Check that the onClick handler is not overwritten on Delete button
const deleteButtons = await screen.findAllByText("Destroy me!")
expect(deleteButtons).toHaveLength(3)
user.click(deleteButtons[0])
const deleteButton = await screen.findByText("Destroy me!")
await user.click(deleteButton)
await waitFor(() => {
expect(data).toEqual({
assets: [{ asset: "my other asset" }, {}],
assets: [],
})
})
})
43 changes: 24 additions & 19 deletions src/controls/ObjectArrayControl.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { Helpers } from "@jsonforms/core"
import {
ArrayControlProps,
Helpers,
ArrayLayoutProps,
composePaths,
createDefaultValue,
findUISchema,
} from "@jsonforms/core"
import { JsonFormsDispatch } from "@jsonforms/react"
import { Flex, List, Button } from "antd"
import range from "lodash.range"
import { useCallback, useEffect } from "react"
import { useCallback, useEffect, useMemo } from "react"
import { ArrayControlOptions } from "../ui-schema"

export function ObjectArrayControl({
Expand All @@ -22,19 +22,33 @@ export function ObjectArrayControl({
removeItems,
renderers,
cells,
visible,
rootSchema,
uischemas,
}: ArrayControlProps) {
required,
}: ArrayLayoutProps) {
const foundUISchema = useMemo(
() =>
findUISchema(
uischemas ?? [],
schema,
uischema.scope,
path,
undefined,
uischema,
rootSchema,
),
[uischemas, schema, path, uischema, rootSchema],
)

const innerCreateDefaultValue = useCallback(
() => createDefaultValue(schema, rootSchema),
[schema, rootSchema],
)

useEffect(() => {
if (data === 0) {
addItem(path, innerCreateDefaultValue())()
}
}, [addItem, data, innerCreateDefaultValue, path])
if (!visible) {
return null
}

const labelDescription = Helpers.createLabelDescriptionFrom(uischema, schema)
const label = labelDescription.show ? labelDescription.text : ""
Expand All @@ -43,15 +57,6 @@ export function ObjectArrayControl({
(uischema.options as ArrayControlOptions) ?? {}

const renderItem = (_item: number, index: number) => {
const foundUISchema = findUISchema(
uischemas ?? [],
schema,
uischema.scope,
path,
undefined,
uischema,
rootSchema,
)
return (
<List.Item
key={index}
Expand All @@ -60,7 +65,7 @@ export function ObjectArrayControl({
key="remove"
children="Delete"
{...options.removeButtonProps}
disabled={!removeItems || (data === 1 && index === 0)}
disabled={!removeItems || (required && data == 1 && index === 0)}
onClick={(e) => {
e.stopPropagation()
removeItems?.(path, [index])()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,38 +42,6 @@ export const ObjectArrayOfStrings: Story = {
},
}

export const ObjectArrayOfBooleans: Story = {
tags: ["autodocs"],
args: {
jsonSchema: {
type: "object",
properties: {
options: {
type: "array",
items: {
type: "object",
properties: {
option: {
title: "Option",
type: "boolean",
},
},
},
},
},
} satisfies JSONSchema,
uiSchema: {
type: "VerticalLayout",
elements: [
{
scope: "#/properties/options",
type: "Control",
},
],
} satisfies UISchema,
},
}

export const ObjectArrayWithUiOptionAddButtonTop: Story = {
tags: ["autodocs"],
args: {
Expand Down Expand Up @@ -176,3 +144,58 @@ export const ObjectArrayWithMultipleProperties: Story = {
} satisfies UISchema,
},
}

export const PrimativeArrayOfStringsAsRequired: Story = {
tags: ["autodocs"],
args: {
jsonSchema: {
type: "object",
properties: {
assets: {
type: "array",
items: {
type: "string",
title: "asset",
},
},
},
required: ["assets"],
} satisfies JSONSchema,
uiSchema: {
type: "VerticalLayout",
elements: [
{
scope: "#/properties/assets",
type: "Control",
},
],
} satisfies UISchema,
},
}

export const PrimativeArrayOfNumbersWithoutRequired: Story = {
tags: ["autodocs"],
args: {
jsonSchema: {
type: "object",
properties: {
assets: {
type: "array",
items: {
type: "number",
title: "asset",
},
},
},
} satisfies JSONSchema,
uiSchema: {
type: "VerticalLayout",
elements: [
{
scope: "#/properties/assets",
type: "Control",
},
],
} satisfies UISchema,
},
}
20 changes: 20 additions & 0 deletions src/testSchemas/objectArraySchema.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,26 @@ export const objectArrayControlJsonSchema = {
},
} satisfies JSONSchema

export const objectArrayControlJsonSchemaWithRequired = {
title: "Assets",
type: "object",
properties: {
assets: {
type: "array",
items: {
type: "object",
properties: {
asset: {
title: "Asset",
type: "string",
},
},
},
},
},
required: ["assets"],
} satisfies JSONSchema

export const objectArrayControlUISchemaWithIcons = {
type: "VerticalLayout",
elements: [
Expand Down

0 comments on commit 368b1e6

Please sign in to comment.