diff --git a/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react-forms.test.ts.snap b/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react-forms.test.ts.snap index 0987df37..35325fe0 100644 --- a/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react-forms.test.ts.snap +++ b/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react-forms.test.ts.snap @@ -1,9 +1,10 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`amplify form renderer tests GraphQL form tests should generate a create form 1`] = ` +exports[`amplify form renderer tests GraphQL form tests should 1:1 relationships without types file path - Create 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { + Autocomplete, Badge, Button, Divider, @@ -12,14 +13,14 @@ import { Icon, ScrollView, Text, - TextAreaField, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { API } from \\"aws-amplify\\"; -import { createPost } from \\"../graphql/mutations\\"; +import { listDogs } from \\"../graphql/queries\\"; +import { createOwner, updateDog, updateOwner } from \\"../graphql/mutations\\"; function ArrayField({ items = [], onChange, @@ -178,63 +179,52 @@ function ArrayField({ ); } -export default function MyPostForm(props) { +export default function CreateOwnerForm(props) { const { clearOnSuccess = true, onSuccess, onError, onSubmit, - onCancel, onValidate, onChange, overrides, ...rest } = props; const initialValues = { - caption: \\"\\", - username: \\"\\", - post_url: \\"\\", - metadata: \\"\\", - profile_url: \\"\\", - nonModelField: \\"\\", - nonModelFieldArray: [], + name: \\"\\", + Dog: undefined, }; - const [caption, setCaption] = React.useState(initialValues.caption); - const [username, setUsername] = React.useState(initialValues.username); - const [post_url, setPost_url] = React.useState(initialValues.post_url); - const [metadata, setMetadata] = React.useState(initialValues.metadata); - const [profile_url, setProfile_url] = React.useState( - initialValues.profile_url - ); - const [nonModelField, setNonModelField] = React.useState( - initialValues.nonModelField - ); - const [nonModelFieldArray, setNonModelFieldArray] = React.useState( - initialValues.nonModelFieldArray - ); + const [name, setName] = React.useState(initialValues.name); + const [Dog, setDog] = React.useState(initialValues.Dog); + const [DogLoading, setDogLoading] = React.useState(false); + const [DogRecords, setDogRecords] = React.useState([]); + const autocompleteLength = 10; const [errors, setErrors] = React.useState({}); const resetStateValues = () => { - setCaption(initialValues.caption); - setUsername(initialValues.username); - setPost_url(initialValues.post_url); - setMetadata(initialValues.metadata); - setProfile_url(initialValues.profile_url); - setNonModelField(initialValues.nonModelField); - setNonModelFieldArray(initialValues.nonModelFieldArray); - setCurrentNonModelFieldArrayValue(\\"\\"); + setName(initialValues.name); + setDog(initialValues.Dog); + setCurrentDogValue(undefined); + setCurrentDogDisplayValue(\\"\\"); setErrors({}); }; - const [currentNonModelFieldArrayValue, setCurrentNonModelFieldArrayValue] = + const [currentDogDisplayValue, setCurrentDogDisplayValue] = React.useState(\\"\\"); - const nonModelFieldArrayRef = React.createRef(); + const [currentDogValue, setCurrentDogValue] = React.useState(undefined); + const DogRef = React.createRef(); + const getIDValue = { + Dog: (r) => JSON.stringify({ id: r?.id }), + }; + const DogIdSet = new Set( + Array.isArray(Dog) + ? Dog.map((r) => getIDValue.Dog?.(r)) + : getIDValue.Dog?.(Dog) + ); + const getDisplayValue = { + Dog: (r) => \`\${r?.name ? r?.name + \\" - \\" : \\"\\"}\${r?.id}\`, + }; const validations = { - caption: [], - username: [], - post_url: [{ type: \\"URL\\" }], - metadata: [{ type: \\"JSON\\" }], - profile_url: [{ type: \\"URL\\" }], - nonModelField: [{ type: \\"JSON\\" }], - nonModelFieldArray: [{ type: \\"JSON\\" }], + name: [{ type: \\"Required\\" }], + Dog: [], }; const runValidationTasks = async ( fieldName, @@ -253,6 +243,35 @@ export default function MyPostForm(props) { setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; + const fetchDogRecords = async (value) => { + setDogLoading(true); + const newOptions = []; + let newNext = \\"\\"; + while (newOptions.length < autocompleteLength && newNext != null) { + const variables = { + limit: autocompleteLength * 5, + filter: { + or: [{ name: { contains: value } }, { id: { contains: value } }], + }, + }; + if (newNext) { + variables[\\"nextToken\\"] = newNext; + } + const result = ( + await API.graphql({ + query: listDogs, + variables, + }) + ).data.listDogs.item; + var loaded = result.filter( + (item) => !DogIdSet.has(getIDValue.Dog?.(item)) + ); + newOptions.push(...loaded); + newNext = result.nextToken; + } + setDogRecords(newOptions.slice(0, autocompleteLength)); + setDogLoading(false); + }; return ( { event.preventDefault(); let modelFields = { - caption, - username, - post_url, - metadata, - profile_url, - nonModelField, - nonModelFieldArray, + name, + Dog, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => - runValidationTasks(fieldName, item) + runValidationTasks( + fieldName, + item, + getDisplayValue[fieldName] + ) ) ); return promises; } promises.push( - runValidationTasks(fieldName, modelFields[fieldName]) + runValidationTasks( + fieldName, + modelFields[fieldName], + getDisplayValue[fieldName] + ) ); return promises; }, []) @@ -298,27 +320,45 @@ export default function MyPostForm(props) { modelFields[key] = undefined; } }); - const modelFieldsToSave = { - caption: modelFields.caption, - username: modelFields.username, - post_url: modelFields.post_url, - metadata: modelFields.metadata, - profile_url: modelFields.profile_url, - nonModelFieldArray: modelFields.nonModelFieldArray.map((s) => - JSON.parse(s) - ), - nonModelField: modelFields.nonModelField - ? JSON.parse(modelFields.nonModelField) - : modelFields.nonModelField, - }; - await API.graphql({ - query: createPost, + const owner = await API.graphql({ + query: createOwner, variables: { input: { - ...modelFieldsToSave, + ...modelFields, }, }, }); + const promises = []; + const dogToLink = modelFields.Dog; + if (dogToLink) { + promises.push( + API.graphql({ + query: updateDog, + variables: { + input: { + ...Dog, + Owner: owner, + }, + }, + }) + ); + const ownerToUnlink = await dogToLink.Owner; + if (ownerToUnlink) { + promises.push( + API.graphql({ + query: updateOwner, + variables: { + input: { + ...ownerToUnlink, + Dog: undefined, + ownerDogId: undefined, + }, + }, + }) + ); + } + } + await Promise.all(promises); if (onSuccess) { onSuccess(modelFields); } @@ -331,338 +371,180 @@ export default function MyPostForm(props) { } } }} - {...getOverrideProps(overrides, \\"MyPostForm\\")} + {...getOverrideProps(overrides, \\"CreateOwnerForm\\")} {...rest} > - - - - - - - { let { value } = e.target; if (onChange) { const modelFields = { - caption: value, - username, - post_url, - metadata, - profile_url, - nonModelField, - nonModelFieldArray, + name: value, + Dog, }; const result = onChange(modelFields); - value = result?.caption ?? value; + value = result?.name ?? value; } - if (errors.caption?.hasError) { - runValidationTasks(\\"caption\\", value); + if (errors.name?.hasError) { + runValidationTasks(\\"name\\", value); } - setCaption(value); + setName(value); }} - onBlur={() => runValidationTasks(\\"caption\\", caption)} - errorMessage={errors.caption?.errorMessage} - hasError={errors.caption?.hasError} - {...getOverrideProps(overrides, \\"caption\\")} + onBlur={() => runValidationTasks(\\"name\\", name)} + errorMessage={errors.name?.errorMessage} + hasError={errors.name?.hasError} + {...getOverrideProps(overrides, \\"name\\")} > - { - let { value } = e.target; + { + let value = items[0]; if (onChange) { const modelFields = { - caption, - username: value, - post_url, - metadata, - profile_url, - nonModelField, - nonModelFieldArray, - }; - const result = onChange(modelFields); - value = result?.username ?? value; - } - if (errors.username?.hasError) { - runValidationTasks(\\"username\\", value); - } - setUsername(value); - }} - onBlur={() => runValidationTasks(\\"username\\", username)} - errorMessage={errors.username?.errorMessage} - hasError={errors.username?.hasError} - {...getOverrideProps(overrides, \\"username\\")} - > - { - let { value } = e.target; - if (onChange) { - const modelFields = { - caption, - username, - post_url: value, - metadata, - profile_url, - nonModelField, - nonModelFieldArray, - }; - const result = onChange(modelFields); - value = result?.post_url ?? value; - } - if (errors.post_url?.hasError) { - runValidationTasks(\\"post_url\\", value); - } - setPost_url(value); - }} - onBlur={() => runValidationTasks(\\"post_url\\", post_url)} - errorMessage={errors.post_url?.errorMessage} - hasError={errors.post_url?.hasError} - {...getOverrideProps(overrides, \\"post_url\\")} - > - { - let { value } = e.target; - if (onChange) { - const modelFields = { - caption, - username, - post_url, - metadata: value, - profile_url, - nonModelField, - nonModelFieldArray, - }; - const result = onChange(modelFields); - value = result?.metadata ?? value; - } - if (errors.metadata?.hasError) { - runValidationTasks(\\"metadata\\", value); - } - setMetadata(value); - }} - onBlur={() => runValidationTasks(\\"metadata\\", metadata)} - errorMessage={errors.metadata?.errorMessage} - hasError={errors.metadata?.hasError} - {...getOverrideProps(overrides, \\"metadata\\")} - > - { - let { value } = e.target; - if (onChange) { - const modelFields = { - caption, - username, - post_url, - metadata, - profile_url: value, - nonModelField, - nonModelFieldArray, - }; - const result = onChange(modelFields); - value = result?.profile_url ?? value; - } - if (errors.profile_url?.hasError) { - runValidationTasks(\\"profile_url\\", value); - } - setProfile_url(value); - }} - onBlur={() => runValidationTasks(\\"profile_url\\", profile_url)} - errorMessage={errors.profile_url?.errorMessage} - hasError={errors.profile_url?.hasError} - {...getOverrideProps(overrides, \\"profile_url\\")} - > - { - let { value } = e.target; - if (onChange) { - const modelFields = { - caption, - username, - post_url, - metadata, - profile_url, - nonModelField: value, - nonModelFieldArray, + name, + Dog: value, }; const result = onChange(modelFields); - value = result?.nonModelField ?? value; - } - if (errors.nonModelField?.hasError) { - runValidationTasks(\\"nonModelField\\", value); + value = result?.Dog ?? value; } - setNonModelField(value); + setDog(value); + setCurrentDogValue(undefined); + setCurrentDogDisplayValue(\\"\\"); }} - onBlur={() => runValidationTasks(\\"nonModelField\\", nonModelField)} - errorMessage={errors.nonModelField?.errorMessage} - hasError={errors.nonModelField?.hasError} - {...getOverrideProps(overrides, \\"nonModelField\\")} - > - { - let values = items; - if (onChange) { - const modelFields = { - caption, - username, - post_url, - metadata, - profile_url, - nonModelField, - nonModelFieldArray: values, - }; - const result = onChange(modelFields); - values = result?.nonModelFieldArray ?? values; - } - setNonModelFieldArray(values); - setCurrentNonModelFieldArrayValue(\\"\\"); + currentFieldValue={currentDogValue} + label={\\"Dog\\"} + items={Dog ? [Dog] : []} + hasError={errors?.Dog?.hasError} + errorMessage={errors?.Dog?.errorMessage} + getBadgeText={getDisplayValue.Dog} + setFieldValue={(model) => { + setCurrentDogDisplayValue(model ? getDisplayValue.Dog(model) : \\"\\"); + setCurrentDogValue(model); }} - currentFieldValue={currentNonModelFieldArrayValue} - label={\\"Non model field array\\"} - items={nonModelFieldArray} - hasError={errors?.nonModelFieldArray?.hasError} - errorMessage={errors?.nonModelFieldArray?.errorMessage} - setFieldValue={setCurrentNonModelFieldArrayValue} - inputFieldRef={nonModelFieldArrayRef} + inputFieldRef={DogRef} defaultFieldValue={\\"\\"} > - !DogIdSet.has(getIDValue.Dog?.(r))) + .map((r) => ({ + id: getIDValue.Dog?.(r), + label: getDisplayValue.Dog?.(r), + }))} + isLoading={DogLoading} + onSelect={({ id, label }) => { + setCurrentDogValue( + dogRecords.find((r) => + Object.entries(JSON.parse(id)).every( + ([key, value]) => r[key] === value + ) + ) + ); + setCurrentDogDisplayValue(label); + runValidationTasks(\\"Dog\\", label); + }} + onClear={() => { + setCurrentDogDisplayValue(\\"\\"); + }} onChange={(e) => { let { value } = e.target; - if (errors.nonModelFieldArray?.hasError) { - runValidationTasks(\\"nonModelFieldArray\\", value); + fetchDogRecords(value); + if (errors.Dog?.hasError) { + runValidationTasks(\\"Dog\\", value); } - setCurrentNonModelFieldArrayValue(value); + setCurrentDogDisplayValue(value); + setCurrentDogValue(undefined); }} - onBlur={() => - runValidationTasks( - \\"nonModelFieldArray\\", - currentNonModelFieldArrayValue - ) - } - errorMessage={errors.nonModelFieldArray?.errorMessage} - hasError={errors.nonModelFieldArray?.hasError} - ref={nonModelFieldArrayRef} + onBlur={() => runValidationTasks(\\"Dog\\", currentDogDisplayValue)} + errorMessage={errors.Dog?.errorMessage} + hasError={errors.Dog?.hasError} + ref={DogRef} labelHidden={true} - {...getOverrideProps(overrides, \\"nonModelFieldArray\\")} - > + {...getOverrideProps(overrides, \\"Dog\\")} + > + + + + + + ); } " `; -exports[`amplify form renderer tests GraphQL form tests should generate a create form 2`] = ` +exports[`amplify form renderer tests GraphQL form tests should 1:1 relationships without types file path - Create 2`] = ` "import * as React from \\"react\\"; -import { GridProps, TextAreaFieldProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; +import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise; -export declare type MyPostFormInputValues = { - caption?: string; - username?: string; - post_url?: string; - metadata?: string; - profile_url?: string; - nonModelField?: string; - nonModelFieldArray?: string[]; +export declare type CreateOwnerFormInputValues = { + name?: string; + Dog?: any; }; -export declare type MyPostFormValidationValues = { - caption?: ValidationFunction; - username?: ValidationFunction; - post_url?: ValidationFunction; - metadata?: ValidationFunction; - profile_url?: ValidationFunction; - nonModelField?: ValidationFunction; - nonModelFieldArray?: ValidationFunction; +export declare type CreateOwnerFormValidationValues = { + name?: ValidationFunction; + Dog?: ValidationFunction; }; export declare type PrimitiveOverrideProps = Partial & React.DOMAttributes; -export declare type MyPostFormOverridesProps = { - MyPostFormGrid?: PrimitiveOverrideProps; - caption?: PrimitiveOverrideProps; - username?: PrimitiveOverrideProps; - post_url?: PrimitiveOverrideProps; - metadata?: PrimitiveOverrideProps; - profile_url?: PrimitiveOverrideProps; - nonModelField?: PrimitiveOverrideProps; - nonModelFieldArray?: PrimitiveOverrideProps; +export declare type CreateOwnerFormOverridesProps = { + CreateOwnerFormGrid?: PrimitiveOverrideProps; + name?: PrimitiveOverrideProps; + Dog?: PrimitiveOverrideProps; } & EscapeHatchProps; -export declare type MyPostFormProps = React.PropsWithChildren<{ - overrides?: MyPostFormOverridesProps | undefined | null; +export declare type CreateOwnerFormProps = React.PropsWithChildren<{ + overrides?: CreateOwnerFormOverridesProps | undefined | null; } & { clearOnSuccess?: boolean; - onSubmit?: (fields: MyPostFormInputValues) => MyPostFormInputValues; - onSuccess?: (fields: MyPostFormInputValues) => void; - onError?: (fields: MyPostFormInputValues, errorMessage: string) => void; - onCancel?: () => void; - onChange?: (fields: MyPostFormInputValues) => MyPostFormInputValues; - onValidate?: MyPostFormValidationValues; + onSubmit?: (fields: CreateOwnerFormInputValues) => CreateOwnerFormInputValues; + onSuccess?: (fields: CreateOwnerFormInputValues) => void; + onError?: (fields: CreateOwnerFormInputValues, errorMessage: string) => void; + onChange?: (fields: CreateOwnerFormInputValues) => CreateOwnerFormInputValues; + onValidate?: CreateOwnerFormValidationValues; } & React.CSSProperties>; -export default function MyPostForm(props: MyPostFormProps): React.ReactElement; +export default function CreateOwnerForm(props: CreateOwnerFormProps): React.ReactElement; " `; -exports[`amplify form renderer tests GraphQL form tests should generate a create form with belongsTo relationship 1`] = ` +exports[`amplify form renderer tests GraphQL form tests should generate a create form 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { - Autocomplete, Badge, Button, Divider, @@ -671,14 +553,14 @@ import { Icon, ScrollView, Text, + TextAreaField, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { API } from \\"aws-amplify\\"; -import { listTeams } from \\"../graphql/queries\\"; -import { createMember } from \\"../graphql/mutations\\"; +import { createPost } from \\"../graphql/mutations\\"; function ArrayField({ items = [], onChange, @@ -837,7 +719,7 @@ function ArrayField({ ); } -export default function MyMemberForm(props) { +export default function MyPostForm(props) { const { clearOnSuccess = true, onSuccess, @@ -850,53 +732,50 @@ export default function MyMemberForm(props) { ...rest } = props; const initialValues = { - name: \\"\\", - teamID: undefined, - Team: undefined, + caption: \\"\\", + username: \\"\\", + post_url: \\"\\", + metadata: \\"\\", + profile_url: \\"\\", + nonModelField: \\"\\", + nonModelFieldArray: [], }; - const [name, setName] = React.useState(initialValues.name); - const [teamID, setTeamID] = React.useState(initialValues.teamID); - const [teamIDLoading, setTeamIDLoading] = React.useState(false); - const [teamIDRecords, setTeamIDRecords] = React.useState([]); - const [Team, setTeam] = React.useState(initialValues.Team); - const [TeamLoading, setTeamLoading] = React.useState(false); - const [TeamRecords, setTeamRecords] = React.useState([]); - const autocompleteLength = 10; + const [caption, setCaption] = React.useState(initialValues.caption); + const [username, setUsername] = React.useState(initialValues.username); + const [post_url, setPost_url] = React.useState(initialValues.post_url); + const [metadata, setMetadata] = React.useState(initialValues.metadata); + const [profile_url, setProfile_url] = React.useState( + initialValues.profile_url + ); + const [nonModelField, setNonModelField] = React.useState( + initialValues.nonModelField + ); + const [nonModelFieldArray, setNonModelFieldArray] = React.useState( + initialValues.nonModelFieldArray + ); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { - setName(initialValues.name); - setTeamID(initialValues.teamID); - setCurrentTeamIDValue(undefined); - setCurrentTeamIDDisplayValue(\\"\\"); - setTeam(initialValues.Team); - setCurrentTeamValue(undefined); - setCurrentTeamDisplayValue(\\"\\"); + setCaption(initialValues.caption); + setUsername(initialValues.username); + setPost_url(initialValues.post_url); + setMetadata(initialValues.metadata); + setProfile_url(initialValues.profile_url); + setNonModelField(initialValues.nonModelField); + setNonModelFieldArray(initialValues.nonModelFieldArray); + setCurrentNonModelFieldArrayValue(\\"\\"); setErrors({}); }; - const [currentTeamIDDisplayValue, setCurrentTeamIDDisplayValue] = - React.useState(\\"\\"); - const [currentTeamIDValue, setCurrentTeamIDValue] = React.useState(undefined); - const teamIDRef = React.createRef(); - const [currentTeamDisplayValue, setCurrentTeamDisplayValue] = + const [currentNonModelFieldArrayValue, setCurrentNonModelFieldArrayValue] = React.useState(\\"\\"); - const [currentTeamValue, setCurrentTeamValue] = React.useState(undefined); - const TeamRef = React.createRef(); - const getIDValue = { - Team: (r) => JSON.stringify({ id: r?.id }), - }; - const TeamIdSet = new Set( - Array.isArray(Team) - ? Team.map((r) => getIDValue.Team?.(r)) - : getIDValue.Team?.(Team) - ); - const getDisplayValue = { - teamID: (r) => \`\${r?.name ? r?.name + \\" - \\" : \\"\\"}\${r?.id}\`, - Team: (r) => r?.name, - }; + const nonModelFieldArrayRef = React.createRef(); const validations = { - name: [], - teamID: [{ type: \\"Required\\" }], - Team: [], + caption: [], + username: [], + post_url: [{ type: \\"URL\\" }], + metadata: [{ type: \\"JSON\\" }], + profile_url: [{ type: \\"URL\\" }], + nonModelField: [{ type: \\"JSON\\" }], + nonModelFieldArray: [{ type: \\"JSON\\" }], }; const runValidationTasks = async ( fieldName, @@ -915,62 +794,6 @@ export default function MyMemberForm(props) { setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; - const fetchTeamIDRecords = async (value) => { - setTeamIDLoading(true); - const newOptions = []; - let newNext = \\"\\"; - while (newOptions.length < autocompleteLength && newNext != null) { - const variables = { - limit: autocompleteLength * 5, - filter: { - or: [{ name: { contains: value } }, { id: { contains: value } }], - }, - }; - if (newNext) { - variables[\\"nextToken\\"] = newNext; - } - const result = ( - await API.graphql({ - query: listTeams, - variables, - }) - ).data.listTeamIDS.item; - var loaded = result.filter( - (item) => !teamIDIdSet.has(getIDValue.teamID?.(item)) - ); - newOptions.push(...loaded); - newNext = result.nextToken; - } - setTeamIDRecords(newOptions.slice(0, autocompleteLength)); - setTeamIDLoading(false); - }; - const fetchTeamRecords = async (value) => { - setTeamLoading(true); - const newOptions = []; - let newNext = \\"\\"; - while (newOptions.length < autocompleteLength && newNext != null) { - const variables = { - limit: autocompleteLength * 5, - filter: { or: [{ name: { contains: value } }] }, - }; - if (newNext) { - variables[\\"nextToken\\"] = newNext; - } - const result = ( - await API.graphql({ - query: listTeams, - variables, - }) - ).data.listTeams.item; - var loaded = result.filter( - (item) => !TeamIdSet.has(getIDValue.Team?.(item)) - ); - newOptions.push(...loaded); - newNext = result.nextToken; - } - setTeamRecords(newOptions.slice(0, autocompleteLength)); - setTeamLoading(false); - }; return ( { event.preventDefault(); let modelFields = { - name, - teamID, - Team, + caption, + username, + post_url, + metadata, + profile_url, + nonModelField, + nonModelFieldArray, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => - runValidationTasks( - fieldName, - item, - getDisplayValue[fieldName] - ) + runValidationTasks(fieldName, item) ) ); return promises; } promises.push( - runValidationTasks( - fieldName, - modelFields[fieldName], - getDisplayValue[fieldName] - ) + runValidationTasks(fieldName, modelFields[fieldName]) ); return promises; }, []) @@ -1020,11 +839,24 @@ export default function MyMemberForm(props) { modelFields[key] = undefined; } }); + const modelFieldsToSave = { + caption: modelFields.caption, + username: modelFields.username, + post_url: modelFields.post_url, + metadata: modelFields.metadata, + profile_url: modelFields.profile_url, + nonModelFieldArray: modelFields.nonModelFieldArray.map((s) => + JSON.parse(s) + ), + nonModelField: modelFields.nonModelField + ? JSON.parse(modelFields.nonModelField) + : modelFields.nonModelField, + }; await API.graphql({ - query: createMember, + query: createPost, variables: { input: { - ...modelFields, + ...modelFieldsToSave, }, }, }); @@ -1040,7 +872,7 @@ export default function MyMemberForm(props) { } } }} - {...getOverrideProps(overrides, \\"MyMemberForm\\")} + {...getOverrideProps(overrides, \\"MyPostForm\\")} {...rest} > { let { value } = e.target; if (onChange) { const modelFields = { - name: value, - teamID, - Team, + caption: value, + username, + post_url, + metadata, + profile_url, + nonModelField, + nonModelFieldArray, }; const result = onChange(modelFields); - value = result?.name ?? value; + value = result?.caption ?? value; } - if (errors.name?.hasError) { - runValidationTasks(\\"name\\", value); + if (errors.caption?.hasError) { + runValidationTasks(\\"caption\\", value); } - setName(value); + setCaption(value); }} - onBlur={() => runValidationTasks(\\"name\\", name)} - errorMessage={errors.name?.errorMessage} - hasError={errors.name?.hasError} - {...getOverrideProps(overrides, \\"name\\")} + onBlur={() => runValidationTasks(\\"caption\\", caption)} + errorMessage={errors.caption?.errorMessage} + hasError={errors.caption?.hasError} + {...getOverrideProps(overrides, \\"caption\\")} + > + { + let { value } = e.target; + if (onChange) { + const modelFields = { + caption, + username: value, + post_url, + metadata, + profile_url, + nonModelField, + nonModelFieldArray, + }; + const result = onChange(modelFields); + value = result?.username ?? value; + } + if (errors.username?.hasError) { + runValidationTasks(\\"username\\", value); + } + setUsername(value); + }} + onBlur={() => runValidationTasks(\\"username\\", username)} + errorMessage={errors.username?.errorMessage} + hasError={errors.username?.hasError} + {...getOverrideProps(overrides, \\"username\\")} + > + { + let { value } = e.target; + if (onChange) { + const modelFields = { + caption, + username, + post_url: value, + metadata, + profile_url, + nonModelField, + nonModelFieldArray, + }; + const result = onChange(modelFields); + value = result?.post_url ?? value; + } + if (errors.post_url?.hasError) { + runValidationTasks(\\"post_url\\", value); + } + setPost_url(value); + }} + onBlur={() => runValidationTasks(\\"post_url\\", post_url)} + errorMessage={errors.post_url?.errorMessage} + hasError={errors.post_url?.hasError} + {...getOverrideProps(overrides, \\"post_url\\")} > + { + let { value } = e.target; + if (onChange) { + const modelFields = { + caption, + username, + post_url, + metadata: value, + profile_url, + nonModelField, + nonModelFieldArray, + }; + const result = onChange(modelFields); + value = result?.metadata ?? value; + } + if (errors.metadata?.hasError) { + runValidationTasks(\\"metadata\\", value); + } + setMetadata(value); + }} + onBlur={() => runValidationTasks(\\"metadata\\", metadata)} + errorMessage={errors.metadata?.errorMessage} + hasError={errors.metadata?.hasError} + {...getOverrideProps(overrides, \\"metadata\\")} + > + { + let { value } = e.target; + if (onChange) { + const modelFields = { + caption, + username, + post_url, + metadata, + profile_url: value, + nonModelField, + nonModelFieldArray, + }; + const result = onChange(modelFields); + value = result?.profile_url ?? value; + } + if (errors.profile_url?.hasError) { + runValidationTasks(\\"profile_url\\", value); + } + setProfile_url(value); + }} + onBlur={() => runValidationTasks(\\"profile_url\\", profile_url)} + errorMessage={errors.profile_url?.errorMessage} + hasError={errors.profile_url?.hasError} + {...getOverrideProps(overrides, \\"profile_url\\")} + > + { + let { value } = e.target; + if (onChange) { + const modelFields = { + caption, + username, + post_url, + metadata, + profile_url, + nonModelField: value, + nonModelFieldArray, + }; + const result = onChange(modelFields); + value = result?.nonModelField ?? value; + } + if (errors.nonModelField?.hasError) { + runValidationTasks(\\"nonModelField\\", value); + } + setNonModelField(value); + }} + onBlur={() => runValidationTasks(\\"nonModelField\\", nonModelField)} + errorMessage={errors.nonModelField?.errorMessage} + hasError={errors.nonModelField?.hasError} + {...getOverrideProps(overrides, \\"nonModelField\\")} + > { - let value = items[0]; + let values = items; if (onChange) { const modelFields = { - name, - teamID: value, - Team, + caption, + username, + post_url, + metadata, + profile_url, + nonModelField, + nonModelFieldArray: values, }; const result = onChange(modelFields); - value = result?.teamID ?? value; + values = result?.nonModelFieldArray ?? values; } - setTeamID(value); - setCurrentTeamIDValue(undefined); - }} - currentFieldValue={currentTeamIDValue} - label={\\"Team id\\"} - items={teamID ? [teamID] : []} - hasError={errors?.teamID?.hasError} - errorMessage={errors?.teamID?.errorMessage} - getBadgeText={(value) => - value - ? getDisplayValue.teamID(teamRecords.find((r) => r.id === value)) - : \\"\\" - } - setFieldValue={(value) => { - setCurrentTeamIDDisplayValue( - value - ? getDisplayValue.teamID(teamRecords.find((r) => r.id === value)) - : \\"\\" - ); - setCurrentTeamIDValue(value); + setNonModelFieldArray(values); + setCurrentNonModelFieldArrayValue(\\"\\"); }} - inputFieldRef={teamIDRef} + currentFieldValue={currentNonModelFieldArrayValue} + label={\\"Non model field array\\"} + items={nonModelFieldArray} + hasError={errors?.nonModelFieldArray?.hasError} + errorMessage={errors?.nonModelFieldArray?.errorMessage} + setFieldValue={setCurrentNonModelFieldArrayValue} + inputFieldRef={nonModelFieldArrayRef} defaultFieldValue={\\"\\"} > - - arr.findIndex((member) => member?.id === r?.id) === i - ) - .map((r) => ({ - id: r?.id, - label: getDisplayValue.teamID?.(r), - }))} - isLoading={teamIDLoading} - onSelect={({ id, label }) => { - setCurrentTeamIDValue(id); - setCurrentTeamIDDisplayValue(label); - runValidationTasks(\\"teamID\\", label); - }} - onClear={() => { - setCurrentTeamIDDisplayValue(\\"\\"); - }} + value={currentNonModelFieldArrayValue} onChange={(e) => { let { value } = e.target; - fetchTeamIDRecords(value); - if (errors.teamID?.hasError) { - runValidationTasks(\\"teamID\\", value); + if (errors.nonModelFieldArray?.hasError) { + runValidationTasks(\\"nonModelFieldArray\\", value); } - setCurrentTeamIDDisplayValue(value); - setCurrentTeamIDValue(undefined); + setCurrentNonModelFieldArrayValue(value); }} - onBlur={() => runValidationTasks(\\"teamID\\", currentTeamIDValue)} - errorMessage={errors.teamID?.errorMessage} - hasError={errors.teamID?.hasError} - ref={teamIDRef} + onBlur={() => + runValidationTasks( + \\"nonModelFieldArray\\", + currentNonModelFieldArrayValue + ) + } + errorMessage={errors.nonModelFieldArray?.errorMessage} + hasError={errors.nonModelFieldArray?.hasError} + ref={nonModelFieldArrayRef} labelHidden={true} - {...getOverrideProps(overrides, \\"teamID\\")} - > + {...getOverrideProps(overrides, \\"nonModelFieldArray\\")} + > - { - let value = items[0]; - if (onChange) { - const modelFields = { - name, + + ); +} +" +`; + +exports[`amplify form renderer tests GraphQL form tests should generate a create form 2`] = ` +"import * as React from \\"react\\"; +import { GridProps, TextAreaFieldProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; +import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; +export declare type ValidationResponse = { + hasError: boolean; + errorMessage?: string; +}; +export declare type ValidationFunction = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise; +export declare type MyPostFormInputValues = { + caption?: string; + username?: string; + post_url?: string; + metadata?: string; + profile_url?: string; + nonModelField?: string; + nonModelFieldArray?: string[]; +}; +export declare type MyPostFormValidationValues = { + caption?: ValidationFunction; + username?: ValidationFunction; + post_url?: ValidationFunction; + metadata?: ValidationFunction; + profile_url?: ValidationFunction; + nonModelField?: ValidationFunction; + nonModelFieldArray?: ValidationFunction; +}; +export declare type PrimitiveOverrideProps = Partial & React.DOMAttributes; +export declare type MyPostFormOverridesProps = { + MyPostFormGrid?: PrimitiveOverrideProps; + caption?: PrimitiveOverrideProps; + username?: PrimitiveOverrideProps; + post_url?: PrimitiveOverrideProps; + metadata?: PrimitiveOverrideProps; + profile_url?: PrimitiveOverrideProps; + nonModelField?: PrimitiveOverrideProps; + nonModelFieldArray?: PrimitiveOverrideProps; +} & EscapeHatchProps; +export declare type MyPostFormProps = React.PropsWithChildren<{ + overrides?: MyPostFormOverridesProps | undefined | null; +} & { + clearOnSuccess?: boolean; + onSubmit?: (fields: MyPostFormInputValues) => MyPostFormInputValues; + onSuccess?: (fields: MyPostFormInputValues) => void; + onError?: (fields: MyPostFormInputValues, errorMessage: string) => void; + onCancel?: () => void; + onChange?: (fields: MyPostFormInputValues) => MyPostFormInputValues; + onValidate?: MyPostFormValidationValues; +} & React.CSSProperties>; +export default function MyPostForm(props: MyPostFormProps): React.ReactElement; +" +`; + +exports[`amplify form renderer tests GraphQL form tests should generate a create form with belongsTo relationship 1`] = ` +"/* eslint-disable */ +import * as React from \\"react\\"; +import { + Autocomplete, + Badge, + Button, + Divider, + Flex, + Grid, + Icon, + ScrollView, + Text, + TextField, + useTheme, +} from \\"@aws-amplify/ui-react\\"; +import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; +import { fetchByPath, validateField } from \\"./utils\\"; +import { API } from \\"aws-amplify\\"; +import { listTeams } from \\"../graphql/queries\\"; +import { createMember } from \\"../graphql/mutations\\"; +function ArrayField({ + items = [], + onChange, + label, + inputFieldRef, + children, + hasError, + setFieldValue, + currentFieldValue, + defaultFieldValue, + lengthLimit, + getBadgeText, + errorMessage, +}) { + const labelElement = {label}; + const { + tokens: { + components: { + fieldmessages: { error: errorStyles }, + }, + }, + } = useTheme(); + const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); + const [isEditing, setIsEditing] = React.useState(); + React.useEffect(() => { + if (isEditing) { + inputFieldRef?.current?.focus(); + } + }, [isEditing]); + const removeItem = async (removeIndex) => { + const newItems = items.filter((value, index) => index !== removeIndex); + await onChange(newItems); + setSelectedBadgeIndex(undefined); + }; + const addItem = async () => { + if ( + currentFieldValue !== undefined && + currentFieldValue !== null && + currentFieldValue !== \\"\\" && + !hasError + ) { + const newItems = [...items]; + if (selectedBadgeIndex !== undefined) { + newItems[selectedBadgeIndex] = currentFieldValue; + setSelectedBadgeIndex(undefined); + } else { + newItems.push(currentFieldValue); + } + await onChange(newItems); + setIsEditing(false); + } + }; + const arraySection = ( + + {!!items?.length && ( + + {items.map((value, index) => { + return ( + { + setSelectedBadgeIndex(index); + setFieldValue(items[index]); + setIsEditing(true); + }} + > + {getBadgeText ? getBadgeText(value) : value.toString()} + { + event.stopPropagation(); + removeItem(index); + }} + /> + + ); + })} + + )} + + + ); + if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { + return ( + + {labelElement} + {arraySection} + + ); + } + return ( + + {labelElement} + {isEditing && children} + {!isEditing ? ( + <> + + {errorMessage && hasError && ( + + {errorMessage} + + )} + + ) : ( + + {(currentFieldValue || isEditing) && ( + + )} + + + )} + {arraySection} + + ); +} +export default function MyMemberForm(props) { + const { + clearOnSuccess = true, + onSuccess, + onError, + onSubmit, + onCancel, + onValidate, + onChange, + overrides, + ...rest + } = props; + const initialValues = { + name: \\"\\", + teamID: undefined, + Team: undefined, + }; + const [name, setName] = React.useState(initialValues.name); + const [teamID, setTeamID] = React.useState(initialValues.teamID); + const [teamIDLoading, setTeamIDLoading] = React.useState(false); + const [teamIDRecords, setTeamIDRecords] = React.useState([]); + const [Team, setTeam] = React.useState(initialValues.Team); + const [TeamLoading, setTeamLoading] = React.useState(false); + const [TeamRecords, setTeamRecords] = React.useState([]); + const autocompleteLength = 10; + const [errors, setErrors] = React.useState({}); + const resetStateValues = () => { + setName(initialValues.name); + setTeamID(initialValues.teamID); + setCurrentTeamIDValue(undefined); + setCurrentTeamIDDisplayValue(\\"\\"); + setTeam(initialValues.Team); + setCurrentTeamValue(undefined); + setCurrentTeamDisplayValue(\\"\\"); + setErrors({}); + }; + const [currentTeamIDDisplayValue, setCurrentTeamIDDisplayValue] = + React.useState(\\"\\"); + const [currentTeamIDValue, setCurrentTeamIDValue] = React.useState(undefined); + const teamIDRef = React.createRef(); + const [currentTeamDisplayValue, setCurrentTeamDisplayValue] = + React.useState(\\"\\"); + const [currentTeamValue, setCurrentTeamValue] = React.useState(undefined); + const TeamRef = React.createRef(); + const getIDValue = { + Team: (r) => JSON.stringify({ id: r?.id }), + }; + const TeamIdSet = new Set( + Array.isArray(Team) + ? Team.map((r) => getIDValue.Team?.(r)) + : getIDValue.Team?.(Team) + ); + const getDisplayValue = { + teamID: (r) => \`\${r?.name ? r?.name + \\" - \\" : \\"\\"}\${r?.id}\`, + Team: (r) => r?.name, + }; + const validations = { + name: [], + teamID: [{ type: \\"Required\\" }], + Team: [], + }; + const runValidationTasks = async ( + fieldName, + currentValue, + getDisplayValue + ) => { + const value = + currentValue && getDisplayValue + ? getDisplayValue(currentValue) + : currentValue; + let validationResponse = validateField(value, validations[fieldName]); + const customValidator = fetchByPath(onValidate, fieldName); + if (customValidator) { + validationResponse = await customValidator(value, validationResponse); + } + setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); + return validationResponse; + }; + const fetchTeamIDRecords = async (value) => { + setTeamIDLoading(true); + const newOptions = []; + let newNext = \\"\\"; + while (newOptions.length < autocompleteLength && newNext != null) { + const variables = { + limit: autocompleteLength * 5, + filter: { + or: [{ name: { contains: value } }, { id: { contains: value } }], + }, + }; + if (newNext) { + variables[\\"nextToken\\"] = newNext; + } + const result = ( + await API.graphql({ + query: listTeams, + variables, + }) + ).data.listTeamIDS.item; + var loaded = result.filter( + (item) => !teamIDIdSet.has(getIDValue.teamID?.(item)) + ); + newOptions.push(...loaded); + newNext = result.nextToken; + } + setTeamIDRecords(newOptions.slice(0, autocompleteLength)); + setTeamIDLoading(false); + }; + const fetchTeamRecords = async (value) => { + setTeamLoading(true); + const newOptions = []; + let newNext = \\"\\"; + while (newOptions.length < autocompleteLength && newNext != null) { + const variables = { + limit: autocompleteLength * 5, + filter: { or: [{ name: { contains: value } }] }, + }; + if (newNext) { + variables[\\"nextToken\\"] = newNext; + } + const result = ( + await API.graphql({ + query: listTeams, + variables, + }) + ).data.listTeams.item; + var loaded = result.filter( + (item) => !TeamIdSet.has(getIDValue.Team?.(item)) + ); + newOptions.push(...loaded); + newNext = result.nextToken; + } + setTeamRecords(newOptions.slice(0, autocompleteLength)); + setTeamLoading(false); + }; + return ( + { + event.preventDefault(); + let modelFields = { + name, + teamID, + Team, + }; + const validationResponses = await Promise.all( + Object.keys(validations).reduce((promises, fieldName) => { + if (Array.isArray(modelFields[fieldName])) { + promises.push( + ...modelFields[fieldName].map((item) => + runValidationTasks( + fieldName, + item, + getDisplayValue[fieldName] + ) + ) + ); + return promises; + } + promises.push( + runValidationTasks( + fieldName, + modelFields[fieldName], + getDisplayValue[fieldName] + ) + ); + return promises; + }, []) + ); + if (validationResponses.some((r) => r.hasError)) { + return; + } + if (onSubmit) { + modelFields = onSubmit(modelFields); + } + try { + Object.entries(modelFields).forEach(([key, value]) => { + if (typeof value === \\"string\\" && value.trim() === \\"\\") { + modelFields[key] = undefined; + } + }); + await API.graphql({ + query: createMember, + variables: { + input: { + ...modelFields, + }, + }, + }); + if (onSuccess) { + onSuccess(modelFields); + } + if (clearOnSuccess) { + resetStateValues(); + } + } catch (err) { + if (onError) { + onError(modelFields, err.message); + } + } + }} + {...getOverrideProps(overrides, \\"MyMemberForm\\")} + {...rest} + > + + + + + + + + { + let { value } = e.target; + if (onChange) { + const modelFields = { + name: value, + teamID, + Team, + }; + const result = onChange(modelFields); + value = result?.name ?? value; + } + if (errors.name?.hasError) { + runValidationTasks(\\"name\\", value); + } + setName(value); + }} + onBlur={() => runValidationTasks(\\"name\\", name)} + errorMessage={errors.name?.errorMessage} + hasError={errors.name?.hasError} + {...getOverrideProps(overrides, \\"name\\")} + > + { + let value = items[0]; + if (onChange) { + const modelFields = { + name, + teamID: value, + Team, + }; + const result = onChange(modelFields); + value = result?.teamID ?? value; + } + setTeamID(value); + setCurrentTeamIDValue(undefined); + }} + currentFieldValue={currentTeamIDValue} + label={\\"Team id\\"} + items={teamID ? [teamID] : []} + hasError={errors?.teamID?.hasError} + errorMessage={errors?.teamID?.errorMessage} + getBadgeText={(value) => + value + ? getDisplayValue.teamID(teamRecords.find((r) => r.id === value)) + : \\"\\" + } + setFieldValue={(value) => { + setCurrentTeamIDDisplayValue( + value + ? getDisplayValue.teamID(teamRecords.find((r) => r.id === value)) + : \\"\\" + ); + setCurrentTeamIDValue(value); + }} + inputFieldRef={teamIDRef} + defaultFieldValue={\\"\\"} + > + + arr.findIndex((member) => member?.id === r?.id) === i + ) + .map((r) => ({ + id: r?.id, + label: getDisplayValue.teamID?.(r), + }))} + isLoading={teamIDLoading} + onSelect={({ id, label }) => { + setCurrentTeamIDValue(id); + setCurrentTeamIDDisplayValue(label); + runValidationTasks(\\"teamID\\", label); + }} + onClear={() => { + setCurrentTeamIDDisplayValue(\\"\\"); + }} + onChange={(e) => { + let { value } = e.target; + fetchTeamIDRecords(value); + if (errors.teamID?.hasError) { + runValidationTasks(\\"teamID\\", value); + } + setCurrentTeamIDDisplayValue(value); + setCurrentTeamIDValue(undefined); + }} + onBlur={() => runValidationTasks(\\"teamID\\", currentTeamIDValue)} + errorMessage={errors.teamID?.errorMessage} + hasError={errors.teamID?.hasError} + ref={teamIDRef} + labelHidden={true} + {...getOverrideProps(overrides, \\"teamID\\")} + > + + { + let value = items[0]; + if (onChange) { + const modelFields = { + name, teamID, Team: value, }; const result = onChange(modelFields); - value = result?.Team ?? value; + value = result?.Team ?? value; + } + setTeam(value); + setCurrentTeamValue(undefined); + setCurrentTeamDisplayValue(\\"\\"); + }} + currentFieldValue={currentTeamValue} + label={\\"Team Label\\"} + items={Team ? [Team] : []} + hasError={errors?.Team?.hasError} + errorMessage={errors?.Team?.errorMessage} + getBadgeText={getDisplayValue.Team} + setFieldValue={(model) => { + setCurrentTeamDisplayValue(model ? getDisplayValue.Team(model) : \\"\\"); + setCurrentTeamValue(model); + }} + inputFieldRef={TeamRef} + defaultFieldValue={\\"\\"} + > + !TeamIdSet.has(getIDValue.Team?.(r))) + .map((r) => ({ + id: getIDValue.Team?.(r), + label: getDisplayValue.Team?.(r), + }))} + isLoading={TeamLoading} + onSelect={({ id, label }) => { + setCurrentTeamValue( + teamRecords.find((r) => + Object.entries(JSON.parse(id)).every( + ([key, value]) => r[key] === value + ) + ) + ); + setCurrentTeamDisplayValue(label); + runValidationTasks(\\"Team\\", label); + }} + onClear={() => { + setCurrentTeamDisplayValue(\\"\\"); + }} + onChange={(e) => { + let { value } = e.target; + fetchTeamRecords(value); + if (errors.Team?.hasError) { + runValidationTasks(\\"Team\\", value); + } + setCurrentTeamDisplayValue(value); + setCurrentTeamValue(undefined); + }} + onBlur={() => runValidationTasks(\\"Team\\", currentTeamDisplayValue)} + errorMessage={errors.Team?.errorMessage} + hasError={errors.Team?.hasError} + ref={TeamRef} + labelHidden={true} + {...getOverrideProps(overrides, \\"Team\\")} + > + + + ); +} +" +`; + +exports[`amplify form renderer tests GraphQL form tests should generate a create form with belongsTo relationship 2`] = ` +"import * as React from \\"react\\"; +import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; +import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; +import { Team } from \\"../API\\"; +export declare type ValidationResponse = { + hasError: boolean; + errorMessage?: string; +}; +export declare type ValidationFunction = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise; +export declare type MyMemberFormInputValues = { + name?: string; + teamID?: string; + Team?: Team; +}; +export declare type MyMemberFormValidationValues = { + name?: ValidationFunction; + teamID?: ValidationFunction; + Team?: ValidationFunction; +}; +export declare type PrimitiveOverrideProps = Partial & React.DOMAttributes; +export declare type MyMemberFormOverridesProps = { + MyMemberFormGrid?: PrimitiveOverrideProps; + name?: PrimitiveOverrideProps; + teamID?: PrimitiveOverrideProps; + Team?: PrimitiveOverrideProps; +} & EscapeHatchProps; +export declare type MyMemberFormProps = React.PropsWithChildren<{ + overrides?: MyMemberFormOverridesProps | undefined | null; +} & { + clearOnSuccess?: boolean; + onSubmit?: (fields: MyMemberFormInputValues) => MyMemberFormInputValues; + onSuccess?: (fields: MyMemberFormInputValues) => void; + onError?: (fields: MyMemberFormInputValues, errorMessage: string) => void; + onCancel?: () => void; + onChange?: (fields: MyMemberFormInputValues) => MyMemberFormInputValues; + onValidate?: MyMemberFormValidationValues; +} & React.CSSProperties>; +export default function MyMemberForm(props: MyMemberFormProps): React.ReactElement; +" +`; + +exports[`amplify form renderer tests GraphQL form tests should generate a create form with hasMany relationship 1`] = ` +"/* eslint-disable */ +import * as React from \\"react\\"; +import { + Autocomplete, + Badge, + Button, + Divider, + Flex, + Grid, + Icon, + ScrollView, + Text, + TextField, + useTheme, +} from \\"@aws-amplify/ui-react\\"; +import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; +import { fetchByPath, validateField } from \\"./utils\\"; +import { API } from \\"aws-amplify\\"; +import { listStudents } from \\"../graphql/queries\\"; +import { createSchool, updateSchool } from \\"../graphql/mutations\\"; +function ArrayField({ + items = [], + onChange, + label, + inputFieldRef, + children, + hasError, + setFieldValue, + currentFieldValue, + defaultFieldValue, + lengthLimit, + getBadgeText, + errorMessage, +}) { + const labelElement = {label}; + const { + tokens: { + components: { + fieldmessages: { error: errorStyles }, + }, + }, + } = useTheme(); + const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); + const [isEditing, setIsEditing] = React.useState(); + React.useEffect(() => { + if (isEditing) { + inputFieldRef?.current?.focus(); + } + }, [isEditing]); + const removeItem = async (removeIndex) => { + const newItems = items.filter((value, index) => index !== removeIndex); + await onChange(newItems); + setSelectedBadgeIndex(undefined); + }; + const addItem = async () => { + if ( + currentFieldValue !== undefined && + currentFieldValue !== null && + currentFieldValue !== \\"\\" && + !hasError + ) { + const newItems = [...items]; + if (selectedBadgeIndex !== undefined) { + newItems[selectedBadgeIndex] = currentFieldValue; + setSelectedBadgeIndex(undefined); + } else { + newItems.push(currentFieldValue); + } + await onChange(newItems); + setIsEditing(false); + } + }; + const arraySection = ( + + {!!items?.length && ( + + {items.map((value, index) => { + return ( + { + setSelectedBadgeIndex(index); + setFieldValue(items[index]); + setIsEditing(true); + }} + > + {getBadgeText ? getBadgeText(value) : value.toString()} + { + event.stopPropagation(); + removeItem(index); + }} + /> + + ); + })} + + )} + + + ); + if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { + return ( + + {labelElement} + {arraySection} + + ); + } + return ( + + {labelElement} + {isEditing && children} + {!isEditing ? ( + <> + + {errorMessage && hasError && ( + + {errorMessage} + + )} + + ) : ( + + {(currentFieldValue || isEditing) && ( + + )} + + + )} + {arraySection} + + ); +} +export default function SchoolCreateForm(props) { + const { + clearOnSuccess = true, + onSuccess, + onError, + onSubmit, + onCancel, + onValidate, + onChange, + overrides, + ...rest + } = props; + const initialValues = { + name: \\"\\", + Students: [], + }; + const [name, setName] = React.useState(initialValues.name); + const [Students, setStudents] = React.useState(initialValues.Students); + const [StudentsLoading, setStudentsLoading] = React.useState(false); + const [StudentsRecords, setStudentsRecords] = React.useState([]); + const autocompleteLength = 10; + const [errors, setErrors] = React.useState({}); + const resetStateValues = () => { + setName(initialValues.name); + setStudents(initialValues.Students); + setCurrentStudentsValue(undefined); + setCurrentStudentsDisplayValue(\\"\\"); + setErrors({}); + }; + const [currentStudentsDisplayValue, setCurrentStudentsDisplayValue] = + React.useState(\\"\\"); + const [currentStudentsValue, setCurrentStudentsValue] = + React.useState(undefined); + const StudentsRef = React.createRef(); + const getIDValue = { + Students: (r) => JSON.stringify({ id: r?.id }), + }; + const StudentsIdSet = new Set( + Array.isArray(Students) + ? Students.map((r) => getIDValue.Students?.(r)) + : getIDValue.Students?.(Students) + ); + const getDisplayValue = { + Students: (r) => r?.name, + }; + const validations = { + name: [], + Students: [], + }; + const runValidationTasks = async ( + fieldName, + currentValue, + getDisplayValue + ) => { + const value = + currentValue && getDisplayValue + ? getDisplayValue(currentValue) + : currentValue; + let validationResponse = validateField(value, validations[fieldName]); + const customValidator = fetchByPath(onValidate, fieldName); + if (customValidator) { + validationResponse = await customValidator(value, validationResponse); + } + setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); + return validationResponse; + }; + const fetchStudentsRecords = async (value) => { + setStudentsLoading(true); + const newOptions = []; + let newNext = \\"\\"; + while (newOptions.length < autocompleteLength && newNext != null) { + const variables = { + limit: autocompleteLength * 5, + filter: { or: [{ name: { contains: value } }] }, + }; + if (newNext) { + variables[\\"nextToken\\"] = newNext; + } + const result = ( + await API.graphql({ + query: listStudents, + variables, + }) + ).data.listStudents.item; + var loaded = result.filter( + (item) => !StudentsIdSet.has(getIDValue.Students?.(item)) + ); + newOptions.push(...loaded); + newNext = result.nextToken; + } + setStudentsRecords(newOptions.slice(0, autocompleteLength)); + setStudentsLoading(false); + }; + return ( + { + event.preventDefault(); + let modelFields = { + name, + Students, + }; + const validationResponses = await Promise.all( + Object.keys(validations).reduce((promises, fieldName) => { + if (Array.isArray(modelFields[fieldName])) { + promises.push( + ...modelFields[fieldName].map((item) => + runValidationTasks( + fieldName, + item, + getDisplayValue[fieldName] + ) + ) + ); + return promises; + } + promises.push( + runValidationTasks( + fieldName, + modelFields[fieldName], + getDisplayValue[fieldName] + ) + ); + return promises; + }, []) + ); + if (validationResponses.some((r) => r.hasError)) { + return; + } + if (onSubmit) { + modelFields = onSubmit(modelFields); + } + try { + Object.entries(modelFields).forEach(([key, value]) => { + if (typeof value === \\"string\\" && value.trim() === \\"\\") { + modelFields[key] = undefined; + } + }); + const modelFieldsToSave = { + name: modelFields.name, + }; + const school = await API.graphql({ + query: createSchool, + variables: { + input: { + ...modelFieldsToSave, + }, + }, + }); + const promises = []; + promises.push( + ...Students.reduce((promises, original) => { + promises.push( + API.graphql({ + query: updateSchool, + variables: { + input: { + ...original, + schoolID: school.id, + }, + }, + }) + ); + return promises; + }, []) + ); + await Promise.all(promises); + if (onSuccess) { + onSuccess(modelFields); + } + if (clearOnSuccess) { + resetStateValues(); + } + } catch (err) { + if (onError) { + onError(modelFields, err.message); + } + } + }} + {...getOverrideProps(overrides, \\"SchoolCreateForm\\")} + {...rest} + > + { + let { value } = e.target; + if (onChange) { + const modelFields = { + name: value, + Students, + }; + const result = onChange(modelFields); + value = result?.name ?? value; + } + if (errors.name?.hasError) { + runValidationTasks(\\"name\\", value); + } + setName(value); + }} + onBlur={() => runValidationTasks(\\"name\\", name)} + errorMessage={errors.name?.errorMessage} + hasError={errors.name?.hasError} + {...getOverrideProps(overrides, \\"name\\")} + > + { + let values = items; + if (onChange) { + const modelFields = { + name, + Students: values, + }; + const result = onChange(modelFields); + values = result?.Students ?? values; } - setTeam(value); - setCurrentTeamValue(undefined); - setCurrentTeamDisplayValue(\\"\\"); + setStudents(values); + setCurrentStudentsValue(undefined); + setCurrentStudentsDisplayValue(\\"\\"); }} - currentFieldValue={currentTeamValue} - label={\\"Team Label\\"} - items={Team ? [Team] : []} - hasError={errors?.Team?.hasError} - errorMessage={errors?.Team?.errorMessage} - getBadgeText={getDisplayValue.Team} + currentFieldValue={currentStudentsValue} + label={\\"Students\\"} + items={Students} + hasError={errors?.Students?.hasError} + errorMessage={errors?.Students?.errorMessage} + getBadgeText={getDisplayValue.Students} setFieldValue={(model) => { - setCurrentTeamDisplayValue(model ? getDisplayValue.Team(model) : \\"\\"); - setCurrentTeamValue(model); + setCurrentStudentsDisplayValue( + model ? getDisplayValue.Students(model) : \\"\\" + ); + setCurrentStudentsValue(model); }} - inputFieldRef={TeamRef} + inputFieldRef={StudentsRef} defaultFieldValue={\\"\\"} > !TeamIdSet.has(getIDValue.Team?.(r))) + placeholder=\\"Search Student\\" + value={currentStudentsDisplayValue} + options={studentsRecords + .filter((r) => !StudentsIdSet.has(getIDValue.Students?.(r))) .map((r) => ({ - id: getIDValue.Team?.(r), - label: getDisplayValue.Team?.(r), + id: getIDValue.Students?.(r), + label: getDisplayValue.Students?.(r), }))} - isLoading={TeamLoading} + isLoading={StudentsLoading} onSelect={({ id, label }) => { - setCurrentTeamValue( - teamRecords.find((r) => + setCurrentStudentsValue( + studentRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); - setCurrentTeamDisplayValue(label); - runValidationTasks(\\"Team\\", label); + setCurrentStudentsDisplayValue(label); + runValidationTasks(\\"Students\\", label); }} onClear={() => { - setCurrentTeamDisplayValue(\\"\\"); + setCurrentStudentsDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; - fetchTeamRecords(value); - if (errors.Team?.hasError) { - runValidationTasks(\\"Team\\", value); + fetchStudentsRecords(value); + if (errors.Students?.hasError) { + runValidationTasks(\\"Students\\", value); } - setCurrentTeamDisplayValue(value); - setCurrentTeamValue(undefined); + setCurrentStudentsDisplayValue(value); + setCurrentStudentsValue(undefined); }} - onBlur={() => runValidationTasks(\\"Team\\", currentTeamDisplayValue)} - errorMessage={errors.Team?.errorMessage} - hasError={errors.Team?.hasError} - ref={TeamRef} + onBlur={() => + runValidationTasks(\\"Students\\", currentStudentsDisplayValue) + } + errorMessage={errors.Students?.errorMessage} + hasError={errors.Students?.hasError} + ref={StudentsRef} labelHidden={true} - {...getOverrideProps(overrides, \\"Team\\")} + {...getOverrideProps(overrides, \\"Students\\")} > + + + + + + + ); } " `; -exports[`amplify form renderer tests GraphQL form tests should generate a create form with belongsTo relationship 2`] = ` +exports[`amplify form renderer tests GraphQL form tests should generate a create form with hasMany relationship 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; -import { Team } from \\"../API\\"; +import { Student } from \\"../API\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise; -export declare type MyMemberFormInputValues = { +export declare type SchoolCreateFormInputValues = { name?: string; - teamID?: string; - Team?: Team; + Students?: Student[]; }; -export declare type MyMemberFormValidationValues = { +export declare type SchoolCreateFormValidationValues = { name?: ValidationFunction; - teamID?: ValidationFunction; - Team?: ValidationFunction; + Students?: ValidationFunction; }; export declare type PrimitiveOverrideProps = Partial & React.DOMAttributes; -export declare type MyMemberFormOverridesProps = { - MyMemberFormGrid?: PrimitiveOverrideProps; +export declare type SchoolCreateFormOverridesProps = { + SchoolCreateFormGrid?: PrimitiveOverrideProps; name?: PrimitiveOverrideProps; - teamID?: PrimitiveOverrideProps; - Team?: PrimitiveOverrideProps; + Students?: PrimitiveOverrideProps; } & EscapeHatchProps; -export declare type MyMemberFormProps = React.PropsWithChildren<{ - overrides?: MyMemberFormOverridesProps | undefined | null; +export declare type SchoolCreateFormProps = React.PropsWithChildren<{ + overrides?: SchoolCreateFormOverridesProps | undefined | null; } & { clearOnSuccess?: boolean; - onSubmit?: (fields: MyMemberFormInputValues) => MyMemberFormInputValues; - onSuccess?: (fields: MyMemberFormInputValues) => void; - onError?: (fields: MyMemberFormInputValues, errorMessage: string) => void; + onSubmit?: (fields: SchoolCreateFormInputValues) => SchoolCreateFormInputValues; + onSuccess?: (fields: SchoolCreateFormInputValues) => void; + onError?: (fields: SchoolCreateFormInputValues, errorMessage: string) => void; onCancel?: () => void; - onChange?: (fields: MyMemberFormInputValues) => MyMemberFormInputValues; - onValidate?: MyMemberFormValidationValues; + onChange?: (fields: SchoolCreateFormInputValues) => SchoolCreateFormInputValues; + onValidate?: SchoolCreateFormValidationValues; } & React.CSSProperties>; -export default function MyMemberForm(props: MyMemberFormProps): React.ReactElement; +export default function SchoolCreateForm(props: SchoolCreateFormProps): React.ReactElement; " `; -exports[`amplify form renderer tests GraphQL form tests should generate a create form with hasMany relationship 1`] = ` +exports[`amplify form renderer tests GraphQL form tests should generate a create form with hasOne relationship 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { @@ -1322,8 +2407,8 @@ import { import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { API } from \\"aws-amplify\\"; -import { listStudents } from \\"../graphql/queries\\"; -import { createSchool, updateSchool } from \\"../graphql/mutations\\"; +import { listAuthors } from \\"../graphql/queries\\"; +import { createBook } from \\"../graphql/mutations\\"; function ArrayField({ items = [], onChange, @@ -1482,7 +2567,7 @@ function ArrayField({ ); } -export default function SchoolCreateForm(props) { +export default function BookCreateForm(props) { const { clearOnSuccess = true, onSuccess, @@ -1496,40 +2581,44 @@ export default function SchoolCreateForm(props) { } = props; const initialValues = { name: \\"\\", - Students: [], + primaryAuthor: undefined, }; const [name, setName] = React.useState(initialValues.name); - const [Students, setStudents] = React.useState(initialValues.Students); - const [StudentsLoading, setStudentsLoading] = React.useState(false); - const [StudentsRecords, setStudentsRecords] = React.useState([]); + const [primaryAuthor, setPrimaryAuthor] = React.useState( + initialValues.primaryAuthor + ); + const [primaryAuthorLoading, setPrimaryAuthorLoading] = React.useState(false); + const [primaryAuthorRecords, setPrimaryAuthorRecords] = React.useState([]); const autocompleteLength = 10; const [errors, setErrors] = React.useState({}); const resetStateValues = () => { setName(initialValues.name); - setStudents(initialValues.Students); - setCurrentStudentsValue(undefined); - setCurrentStudentsDisplayValue(\\"\\"); + setPrimaryAuthor(initialValues.primaryAuthor); + setCurrentPrimaryAuthorValue(undefined); + setCurrentPrimaryAuthorDisplayValue(\\"\\"); setErrors({}); }; - const [currentStudentsDisplayValue, setCurrentStudentsDisplayValue] = - React.useState(\\"\\"); - const [currentStudentsValue, setCurrentStudentsValue] = + const [ + currentPrimaryAuthorDisplayValue, + setCurrentPrimaryAuthorDisplayValue, + ] = React.useState(\\"\\"); + const [currentPrimaryAuthorValue, setCurrentPrimaryAuthorValue] = React.useState(undefined); - const StudentsRef = React.createRef(); + const primaryAuthorRef = React.createRef(); const getIDValue = { - Students: (r) => JSON.stringify({ id: r?.id }), + primaryAuthor: (r) => JSON.stringify({ id: r?.id }), }; - const StudentsIdSet = new Set( - Array.isArray(Students) - ? Students.map((r) => getIDValue.Students?.(r)) - : getIDValue.Students?.(Students) + const primaryAuthorIdSet = new Set( + Array.isArray(primaryAuthor) + ? primaryAuthor.map((r) => getIDValue.primaryAuthor?.(r)) + : getIDValue.primaryAuthor?.(primaryAuthor) ); const getDisplayValue = { - Students: (r) => r?.name, + primaryAuthor: (r) => \`\${r?.name ? r?.name + \\" - \\" : \\"\\"}\${r?.id}\`, }; const validations = { name: [], - Students: [], + primaryAuthor: [], }; const runValidationTasks = async ( fieldName, @@ -1548,32 +2637,34 @@ export default function SchoolCreateForm(props) { setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; - const fetchStudentsRecords = async (value) => { - setStudentsLoading(true); + const fetchPrimaryAuthorRecords = async (value) => { + setPrimaryAuthorLoading(true); const newOptions = []; let newNext = \\"\\"; while (newOptions.length < autocompleteLength && newNext != null) { const variables = { limit: autocompleteLength * 5, - filter: { or: [{ name: { contains: value } }] }, + filter: { + or: [{ name: { contains: value } }, { id: { contains: value } }], + }, }; if (newNext) { variables[\\"nextToken\\"] = newNext; } const result = ( await API.graphql({ - query: listStudents, + query: listAuthors, variables, }) - ).data.listStudents.item; + ).data.listPrimaryAuthors.item; var loaded = result.filter( - (item) => !StudentsIdSet.has(getIDValue.Students?.(item)) + (item) => !primaryAuthorIdSet.has(getIDValue.primaryAuthor?.(item)) ); newOptions.push(...loaded); newNext = result.nextToken; } - setStudentsRecords(newOptions.slice(0, autocompleteLength)); - setStudentsLoading(false); + setPrimaryAuthorRecords(newOptions.slice(0, autocompleteLength)); + setPrimaryAuthorLoading(false); }; return ( { @@ -1623,35 +2714,14 @@ export default function SchoolCreateForm(props) { modelFields[key] = undefined; } }); - const modelFieldsToSave = { - name: modelFields.name, - }; - const school = await API.graphql({ - query: createSchool, + await API.graphql({ + query: createBook, variables: { input: { - ...modelFieldsToSave, + ...modelFields, }, }, }); - const promises = []; - promises.push( - ...Students.reduce((promises, original) => { - promises.push( - API.graphql({ - query: updateSchool, - variables: { - input: { - ...original, - schoolID: school.id, - }, - }, - }) - ); - return promises; - }, []) - ); - await Promise.all(promises); if (onSuccess) { onSuccess(modelFields); } @@ -1664,9 +2734,43 @@ export default function SchoolCreateForm(props) { } } }} - {...getOverrideProps(overrides, \\"SchoolCreateForm\\")} + {...getOverrideProps(overrides, \\"BookCreateForm\\")} {...rest} > + + + + + + + { - let values = items; + let value = items[0]; if (onChange) { const modelFields = { name, - Students: values, + primaryAuthor: value, }; const result = onChange(modelFields); - values = result?.Students ?? values; + value = result?.primaryAuthor ?? value; } - setStudents(values); - setCurrentStudentsValue(undefined); - setCurrentStudentsDisplayValue(\\"\\"); + setPrimaryAuthor(value); + setCurrentPrimaryAuthorValue(undefined); + setCurrentPrimaryAuthorDisplayValue(\\"\\"); }} - currentFieldValue={currentStudentsValue} - label={\\"Students\\"} - items={Students} - hasError={errors?.Students?.hasError} - errorMessage={errors?.Students?.errorMessage} - getBadgeText={getDisplayValue.Students} + currentFieldValue={currentPrimaryAuthorValue} + label={\\"Primary author\\"} + items={primaryAuthor ? [primaryAuthor] : []} + hasError={errors?.primaryAuthor?.hasError} + errorMessage={errors?.primaryAuthor?.errorMessage} + getBadgeText={getDisplayValue.primaryAuthor} setFieldValue={(model) => { - setCurrentStudentsDisplayValue( - model ? getDisplayValue.Students(model) : \\"\\" + setCurrentPrimaryAuthorDisplayValue( + model ? getDisplayValue.primaryAuthor(model) : \\"\\" ); - setCurrentStudentsValue(model); + setCurrentPrimaryAuthorValue(model); }} - inputFieldRef={StudentsRef} + inputFieldRef={primaryAuthorRef} defaultFieldValue={\\"\\"} > !StudentsIdSet.has(getIDValue.Students?.(r))) + placeholder=\\"Search Author\\" + value={currentPrimaryAuthorDisplayValue} + options={primaryAuthorRecords + .filter( + (r) => !primaryAuthorIdSet.has(getIDValue.primaryAuthor?.(r)) + ) .map((r) => ({ - id: getIDValue.Students?.(r), - label: getDisplayValue.Students?.(r), + id: getIDValue.primaryAuthor?.(r), + label: getDisplayValue.primaryAuthor?.(r), }))} - isLoading={StudentsLoading} + isLoading={primaryAuthorLoading} onSelect={({ id, label }) => { - setCurrentStudentsValue( - studentRecords.find((r) => + setCurrentPrimaryAuthorValue( + authorRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); - setCurrentStudentsDisplayValue(label); - runValidationTasks(\\"Students\\", label); + setCurrentPrimaryAuthorDisplayValue(label); + runValidationTasks(\\"primaryAuthor\\", label); }} onClear={() => { - setCurrentStudentsDisplayValue(\\"\\"); + setCurrentPrimaryAuthorDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; - fetchStudentsRecords(value); - if (errors.Students?.hasError) { - runValidationTasks(\\"Students\\", value); + fetchPrimaryAuthorRecords(value); + if (errors.primaryAuthor?.hasError) { + runValidationTasks(\\"primaryAuthor\\", value); } - setCurrentStudentsDisplayValue(value); - setCurrentStudentsValue(undefined); + setCurrentPrimaryAuthorDisplayValue(value); + setCurrentPrimaryAuthorValue(undefined); }} onBlur={() => - runValidationTasks(\\"Students\\", currentStudentsDisplayValue) + runValidationTasks( + \\"primaryAuthor\\", + currentPrimaryAuthorDisplayValue + ) } - errorMessage={errors.Students?.errorMessage} - hasError={errors.Students?.hasError} - ref={StudentsRef} + errorMessage={errors.primaryAuthor?.errorMessage} + hasError={errors.primaryAuthor?.hasError} + ref={primaryAuthorRef} labelHidden={true} - {...getOverrideProps(overrides, \\"Students\\")} + {...getOverrideProps(overrides, \\"primaryAuthor\\")} > - - - - - - - ); } " `; -exports[`amplify form renderer tests GraphQL form tests should generate a create form with hasMany relationship 2`] = ` +exports[`amplify form renderer tests GraphQL form tests should generate a create form with hasOne relationship 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; -import { Student } from \\"../API\\"; +import { Author } from \\"../API\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise; -export declare type SchoolCreateFormInputValues = { +export declare type BookCreateFormInputValues = { name?: string; - Students?: Student[]; + primaryAuthor?: Author; }; -export declare type SchoolCreateFormValidationValues = { +export declare type BookCreateFormValidationValues = { name?: ValidationFunction; - Students?: ValidationFunction; + primaryAuthor?: ValidationFunction; }; export declare type PrimitiveOverrideProps = Partial & React.DOMAttributes; -export declare type SchoolCreateFormOverridesProps = { - SchoolCreateFormGrid?: PrimitiveOverrideProps; +export declare type BookCreateFormOverridesProps = { + BookCreateFormGrid?: PrimitiveOverrideProps; name?: PrimitiveOverrideProps; - Students?: PrimitiveOverrideProps; + primaryAuthor?: PrimitiveOverrideProps; } & EscapeHatchProps; -export declare type SchoolCreateFormProps = React.PropsWithChildren<{ - overrides?: SchoolCreateFormOverridesProps | undefined | null; +export declare type BookCreateFormProps = React.PropsWithChildren<{ + overrides?: BookCreateFormOverridesProps | undefined | null; } & { clearOnSuccess?: boolean; - onSubmit?: (fields: SchoolCreateFormInputValues) => SchoolCreateFormInputValues; - onSuccess?: (fields: SchoolCreateFormInputValues) => void; - onError?: (fields: SchoolCreateFormInputValues, errorMessage: string) => void; + onSubmit?: (fields: BookCreateFormInputValues) => BookCreateFormInputValues; + onSuccess?: (fields: BookCreateFormInputValues) => void; + onError?: (fields: BookCreateFormInputValues, errorMessage: string) => void; onCancel?: () => void; - onChange?: (fields: SchoolCreateFormInputValues) => SchoolCreateFormInputValues; - onValidate?: SchoolCreateFormValidationValues; + onChange?: (fields: BookCreateFormInputValues) => BookCreateFormInputValues; + onValidate?: BookCreateFormValidationValues; } & React.CSSProperties>; -export default function SchoolCreateForm(props: SchoolCreateFormProps): React.ReactElement; +export default function BookCreateForm(props: BookCreateFormProps): React.ReactElement; " `; -exports[`amplify form renderer tests GraphQL form tests should generate a create form with hasOne relationship 1`] = ` +exports[`amplify form renderer tests GraphQL form tests should generate a create form with manyToMany relationship 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { @@ -1859,6 +2935,7 @@ import { Grid, Icon, ScrollView, + SelectField, Text, TextField, useTheme, @@ -1866,8 +2943,8 @@ import { import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { API } from \\"aws-amplify\\"; -import { listAuthors } from \\"../graphql/queries\\"; -import { createBook } from \\"../graphql/mutations\\"; +import { listPosts } from \\"../graphql/queries\\"; +import { createTag, createTagPost } from \\"../graphql/mutations\\"; function ArrayField({ items = [], onChange, @@ -2026,7 +3103,7 @@ function ArrayField({ ); } -export default function BookCreateForm(props) { +export default function TagCreateForm(props) { const { clearOnSuccess = true, onSuccess, @@ -2039,45 +3116,55 @@ export default function BookCreateForm(props) { ...rest } = props; const initialValues = { - name: \\"\\", - primaryAuthor: undefined, + label: \\"\\", + Posts: [], + statuses: [], }; - const [name, setName] = React.useState(initialValues.name); - const [primaryAuthor, setPrimaryAuthor] = React.useState( - initialValues.primaryAuthor - ); - const [primaryAuthorLoading, setPrimaryAuthorLoading] = React.useState(false); - const [primaryAuthorRecords, setPrimaryAuthorRecords] = React.useState([]); + const [label, setLabel] = React.useState(initialValues.label); + const [Posts, setPosts] = React.useState(initialValues.Posts); + const [PostsLoading, setPostsLoading] = React.useState(false); + const [PostsRecords, setPostsRecords] = React.useState([]); + const [statuses, setStatuses] = React.useState(initialValues.statuses); const autocompleteLength = 10; const [errors, setErrors] = React.useState({}); const resetStateValues = () => { - setName(initialValues.name); - setPrimaryAuthor(initialValues.primaryAuthor); - setCurrentPrimaryAuthorValue(undefined); - setCurrentPrimaryAuthorDisplayValue(\\"\\"); + setLabel(initialValues.label); + setPosts(initialValues.Posts); + setCurrentPostsValue(undefined); + setCurrentPostsDisplayValue(\\"\\"); + setStatuses(initialValues.statuses); + setCurrentStatusesValue(\\"\\"); setErrors({}); }; - const [ - currentPrimaryAuthorDisplayValue, - setCurrentPrimaryAuthorDisplayValue, - ] = React.useState(\\"\\"); - const [currentPrimaryAuthorValue, setCurrentPrimaryAuthorValue] = - React.useState(undefined); - const primaryAuthorRef = React.createRef(); + const [currentPostsDisplayValue, setCurrentPostsDisplayValue] = + React.useState(\\"\\"); + const [currentPostsValue, setCurrentPostsValue] = React.useState(undefined); + const PostsRef = React.createRef(); + const [currentStatusesValue, setCurrentStatusesValue] = React.useState(\\"\\"); + const statusesRef = React.createRef(); const getIDValue = { - primaryAuthor: (r) => JSON.stringify({ id: r?.id }), + Posts: (r) => JSON.stringify({ id: r?.id }), }; - const primaryAuthorIdSet = new Set( - Array.isArray(primaryAuthor) - ? primaryAuthor.map((r) => getIDValue.primaryAuthor?.(r)) - : getIDValue.primaryAuthor?.(primaryAuthor) + const PostsIdSet = new Set( + Array.isArray(Posts) + ? Posts.map((r) => getIDValue.Posts?.(r)) + : getIDValue.Posts?.(Posts) ); const getDisplayValue = { - primaryAuthor: (r) => \`\${r?.name ? r?.name + \\" - \\" : \\"\\"}\${r?.id}\`, + Posts: (r) => r?.title, + statuses: (r) => { + const enumDisplayValueMap = { + PENDING: \\"Pending\\", + POSTED: \\"Posted\\", + IN_REVIEW: \\"In review\\", + }; + return enumDisplayValueMap[r]; + }, }; const validations = { - name: [], - primaryAuthor: [], + label: [], + Posts: [], + statuses: [], }; const runValidationTasks = async ( fieldName, @@ -2096,34 +3183,32 @@ export default function BookCreateForm(props) { setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; - const fetchPrimaryAuthorRecords = async (value) => { - setPrimaryAuthorLoading(true); + const fetchPostsRecords = async (value) => { + setPostsLoading(true); const newOptions = []; let newNext = \\"\\"; while (newOptions.length < autocompleteLength && newNext != null) { - const variables = { - limit: autocompleteLength * 5, - filter: { - or: [{ name: { contains: value } }, { id: { contains: value } }], - }, + const variables = { + limit: autocompleteLength * 5, + filter: { or: [{ title: { contains: value } }] }, }; if (newNext) { variables[\\"nextToken\\"] = newNext; } const result = ( await API.graphql({ - query: listAuthors, + query: listPosts, variables, }) - ).data.listPrimaryAuthors.item; + ).data.listPosts.item; var loaded = result.filter( - (item) => !primaryAuthorIdSet.has(getIDValue.primaryAuthor?.(item)) + (item) => !PostsIdSet.has(getIDValue.Posts?.(item)) ); newOptions.push(...loaded); newNext = result.nextToken; } - setPrimaryAuthorRecords(newOptions.slice(0, autocompleteLength)); - setPrimaryAuthorLoading(false); + setPostsRecords(newOptions.slice(0, autocompleteLength)); + setPostsLoading(false); }; return ( { event.preventDefault(); let modelFields = { - name, - primaryAuthor, + label, + Posts, + statuses, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { @@ -2173,14 +3259,36 @@ export default function BookCreateForm(props) { modelFields[key] = undefined; } }); - await API.graphql({ - query: createBook, + const modelFieldsToSave = { + label: modelFields.label, + statuses: modelFields.statuses, + }; + const tag = await API.graphql({ + query: createTag, variables: { input: { - ...modelFields, + ...modelFieldsToSave, }, }, }); + const promises = []; + promises.push( + ...Posts.reduce((promises, post) => { + promises.push( + API.graphql({ + query: createTagPost, + variables: { + input: { + tag, + post, + }, + }, + }) + ); + return promises; + }, []) + ); + await Promise.all(promises); if (onSuccess) { onSuccess(modelFields); } @@ -2193,196 +3301,252 @@ export default function BookCreateForm(props) { } } }} - {...getOverrideProps(overrides, \\"BookCreateForm\\")} + {...getOverrideProps(overrides, \\"TagCreateForm\\")} {...rest} > - - - - - - - { let { value } = e.target; if (onChange) { const modelFields = { - name: value, - primaryAuthor, + label: value, + Posts, + statuses, }; const result = onChange(modelFields); - value = result?.name ?? value; + value = result?.label ?? value; } - if (errors.name?.hasError) { - runValidationTasks(\\"name\\", value); + if (errors.label?.hasError) { + runValidationTasks(\\"label\\", value); } - setName(value); + setLabel(value); }} - onBlur={() => runValidationTasks(\\"name\\", name)} - errorMessage={errors.name?.errorMessage} - hasError={errors.name?.hasError} - {...getOverrideProps(overrides, \\"name\\")} + onBlur={() => runValidationTasks(\\"label\\", label)} + errorMessage={errors.label?.errorMessage} + hasError={errors.label?.hasError} + {...getOverrideProps(overrides, \\"label\\")} > { - let value = items[0]; + let values = items; if (onChange) { const modelFields = { - name, - primaryAuthor: value, + label, + Posts: values, + statuses, }; const result = onChange(modelFields); - value = result?.primaryAuthor ?? value; + values = result?.Posts ?? values; } - setPrimaryAuthor(value); - setCurrentPrimaryAuthorValue(undefined); - setCurrentPrimaryAuthorDisplayValue(\\"\\"); + setPosts(values); + setCurrentPostsValue(undefined); + setCurrentPostsDisplayValue(\\"\\"); }} - currentFieldValue={currentPrimaryAuthorValue} - label={\\"Primary author\\"} - items={primaryAuthor ? [primaryAuthor] : []} - hasError={errors?.primaryAuthor?.hasError} - errorMessage={errors?.primaryAuthor?.errorMessage} - getBadgeText={getDisplayValue.primaryAuthor} + currentFieldValue={currentPostsValue} + label={\\"Posts\\"} + items={Posts} + hasError={errors?.Posts?.hasError} + errorMessage={errors?.Posts?.errorMessage} + getBadgeText={getDisplayValue.Posts} setFieldValue={(model) => { - setCurrentPrimaryAuthorDisplayValue( - model ? getDisplayValue.primaryAuthor(model) : \\"\\" + setCurrentPostsDisplayValue( + model ? getDisplayValue.Posts(model) : \\"\\" ); - setCurrentPrimaryAuthorValue(model); + setCurrentPostsValue(model); }} - inputFieldRef={primaryAuthorRef} + inputFieldRef={PostsRef} defaultFieldValue={\\"\\"} > !primaryAuthorIdSet.has(getIDValue.primaryAuthor?.(r)) - ) - .map((r) => ({ - id: getIDValue.primaryAuthor?.(r), - label: getDisplayValue.primaryAuthor?.(r), - }))} - isLoading={primaryAuthorLoading} + placeholder=\\"Search Post\\" + value={currentPostsDisplayValue} + options={postsRecords.map((r) => ({ + id: getIDValue.Posts?.(r), + label: getDisplayValue.Posts?.(r), + }))} + isLoading={PostsLoading} onSelect={({ id, label }) => { - setCurrentPrimaryAuthorValue( - authorRecords.find((r) => + setCurrentPostsValue( + postRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); - setCurrentPrimaryAuthorDisplayValue(label); - runValidationTasks(\\"primaryAuthor\\", label); + setCurrentPostsDisplayValue(label); + runValidationTasks(\\"Posts\\", label); }} onClear={() => { - setCurrentPrimaryAuthorDisplayValue(\\"\\"); + setCurrentPostsDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; - fetchPrimaryAuthorRecords(value); - if (errors.primaryAuthor?.hasError) { - runValidationTasks(\\"primaryAuthor\\", value); + fetchPostsRecords(value); + if (errors.Posts?.hasError) { + runValidationTasks(\\"Posts\\", value); } - setCurrentPrimaryAuthorDisplayValue(value); - setCurrentPrimaryAuthorValue(undefined); + setCurrentPostsDisplayValue(value); + setCurrentPostsValue(undefined); }} - onBlur={() => - runValidationTasks( - \\"primaryAuthor\\", - currentPrimaryAuthorDisplayValue - ) - } - errorMessage={errors.primaryAuthor?.errorMessage} - hasError={errors.primaryAuthor?.hasError} - ref={primaryAuthorRef} + onBlur={() => runValidationTasks(\\"Posts\\", currentPostsDisplayValue)} + errorMessage={errors.Posts?.errorMessage} + hasError={errors.Posts?.hasError} + ref={PostsRef} labelHidden={true} - {...getOverrideProps(overrides, \\"primaryAuthor\\")} + {...getOverrideProps(overrides, \\"Posts\\")} > + { + let values = items; + if (onChange) { + const modelFields = { + label, + Posts, + statuses: values, + }; + const result = onChange(modelFields); + values = result?.statuses ?? values; + } + setStatuses(values); + setCurrentStatusesValue(\\"\\"); + }} + currentFieldValue={currentStatusesValue} + label={\\"Statuses\\"} + items={statuses} + hasError={errors?.statuses?.hasError} + errorMessage={errors?.statuses?.errorMessage} + getBadgeText={getDisplayValue.statuses} + setFieldValue={setCurrentStatusesValue} + inputFieldRef={statusesRef} + defaultFieldValue={\\"\\"} + > + { + let { value } = e.target; + if (errors.statuses?.hasError) { + runValidationTasks(\\"statuses\\", value); + } + setCurrentStatusesValue(value); + }} + onBlur={() => runValidationTasks(\\"statuses\\", currentStatusesValue)} + errorMessage={errors.statuses?.errorMessage} + hasError={errors.statuses?.hasError} + ref={statusesRef} + labelHidden={true} + {...getOverrideProps(overrides, \\"statuses\\")} + > + + + + + + + + + + + + ); } " `; -exports[`amplify form renderer tests GraphQL form tests should generate a create form with hasOne relationship 2`] = ` +exports[`amplify form renderer tests GraphQL form tests should generate a create form with manyToMany relationship 2`] = ` "import * as React from \\"react\\"; -import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; +import { AutocompleteProps, GridProps, SelectFieldProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; -import { Author } from \\"../API\\"; +import { Post } from \\"../API\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise; -export declare type BookCreateFormInputValues = { - name?: string; - primaryAuthor?: Author; +export declare type TagCreateFormInputValues = { + label?: string; + Posts?: Post[]; + statuses?: string[]; }; -export declare type BookCreateFormValidationValues = { - name?: ValidationFunction; - primaryAuthor?: ValidationFunction; +export declare type TagCreateFormValidationValues = { + label?: ValidationFunction; + Posts?: ValidationFunction; + statuses?: ValidationFunction; }; export declare type PrimitiveOverrideProps = Partial & React.DOMAttributes; -export declare type BookCreateFormOverridesProps = { - BookCreateFormGrid?: PrimitiveOverrideProps; - name?: PrimitiveOverrideProps; - primaryAuthor?: PrimitiveOverrideProps; +export declare type TagCreateFormOverridesProps = { + TagCreateFormGrid?: PrimitiveOverrideProps; + label?: PrimitiveOverrideProps; + Posts?: PrimitiveOverrideProps; + statuses?: PrimitiveOverrideProps; } & EscapeHatchProps; -export declare type BookCreateFormProps = React.PropsWithChildren<{ - overrides?: BookCreateFormOverridesProps | undefined | null; +export declare type TagCreateFormProps = React.PropsWithChildren<{ + overrides?: TagCreateFormOverridesProps | undefined | null; } & { clearOnSuccess?: boolean; - onSubmit?: (fields: BookCreateFormInputValues) => BookCreateFormInputValues; - onSuccess?: (fields: BookCreateFormInputValues) => void; - onError?: (fields: BookCreateFormInputValues, errorMessage: string) => void; + onSubmit?: (fields: TagCreateFormInputValues) => TagCreateFormInputValues; + onSuccess?: (fields: TagCreateFormInputValues) => void; + onError?: (fields: TagCreateFormInputValues, errorMessage: string) => void; onCancel?: () => void; - onChange?: (fields: BookCreateFormInputValues) => BookCreateFormInputValues; - onValidate?: BookCreateFormValidationValues; + onChange?: (fields: TagCreateFormInputValues) => TagCreateFormInputValues; + onValidate?: TagCreateFormValidationValues; } & React.CSSProperties>; -export default function BookCreateForm(props: BookCreateFormProps): React.ReactElement; +export default function TagCreateForm(props: TagCreateFormProps): React.ReactElement; " `; -exports[`amplify form renderer tests GraphQL form tests should generate a create form with manyToMany relationship 1`] = ` +exports[`amplify form renderer tests GraphQL form tests should generate a create form with multiple hasOne relationships 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { @@ -2394,7 +3558,6 @@ import { Grid, Icon, ScrollView, - SelectField, Text, TextField, useTheme, @@ -2402,8 +3565,8 @@ import { import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { API } from \\"aws-amplify\\"; -import { listPosts } from \\"../graphql/queries\\"; -import { createTag, createTagPost } from \\"../graphql/mutations\\"; +import { listAuthors, listTitles } from \\"../graphql/queries\\"; +import { createBook } from \\"../graphql/mutations\\"; function ArrayField({ items = [], onChange, @@ -2562,7 +3725,7 @@ function ArrayField({ ); } -export default function TagCreateForm(props) { +export default function BookCreateForm(props) { const { clearOnSuccess = true, onSuccess, @@ -2575,55 +3738,67 @@ export default function TagCreateForm(props) { ...rest } = props; const initialValues = { - label: \\"\\", - Posts: [], - statuses: [], + name: \\"\\", + primaryAuthor: undefined, + primaryTitle: undefined, }; - const [label, setLabel] = React.useState(initialValues.label); - const [Posts, setPosts] = React.useState(initialValues.Posts); - const [PostsLoading, setPostsLoading] = React.useState(false); - const [PostsRecords, setPostsRecords] = React.useState([]); - const [statuses, setStatuses] = React.useState(initialValues.statuses); + const [name, setName] = React.useState(initialValues.name); + const [primaryAuthor, setPrimaryAuthor] = React.useState( + initialValues.primaryAuthor + ); + const [primaryAuthorLoading, setPrimaryAuthorLoading] = React.useState(false); + const [primaryAuthorRecords, setPrimaryAuthorRecords] = React.useState([]); + const [primaryTitle, setPrimaryTitle] = React.useState( + initialValues.primaryTitle + ); + const [primaryTitleLoading, setPrimaryTitleLoading] = React.useState(false); + const [primaryTitleRecords, setPrimaryTitleRecords] = React.useState([]); const autocompleteLength = 10; const [errors, setErrors] = React.useState({}); const resetStateValues = () => { - setLabel(initialValues.label); - setPosts(initialValues.Posts); - setCurrentPostsValue(undefined); - setCurrentPostsDisplayValue(\\"\\"); - setStatuses(initialValues.statuses); - setCurrentStatusesValue(\\"\\"); + setName(initialValues.name); + setPrimaryAuthor(initialValues.primaryAuthor); + setCurrentPrimaryAuthorValue(undefined); + setCurrentPrimaryAuthorDisplayValue(\\"\\"); + setPrimaryTitle(initialValues.primaryTitle); + setCurrentPrimaryTitleValue(undefined); + setCurrentPrimaryTitleDisplayValue(\\"\\"); setErrors({}); }; - const [currentPostsDisplayValue, setCurrentPostsDisplayValue] = + const [ + currentPrimaryAuthorDisplayValue, + setCurrentPrimaryAuthorDisplayValue, + ] = React.useState(\\"\\"); + const [currentPrimaryAuthorValue, setCurrentPrimaryAuthorValue] = + React.useState(undefined); + const primaryAuthorRef = React.createRef(); + const [currentPrimaryTitleDisplayValue, setCurrentPrimaryTitleDisplayValue] = React.useState(\\"\\"); - const [currentPostsValue, setCurrentPostsValue] = React.useState(undefined); - const PostsRef = React.createRef(); - const [currentStatusesValue, setCurrentStatusesValue] = React.useState(\\"\\"); - const statusesRef = React.createRef(); + const [currentPrimaryTitleValue, setCurrentPrimaryTitleValue] = + React.useState(undefined); + const primaryTitleRef = React.createRef(); const getIDValue = { - Posts: (r) => JSON.stringify({ id: r?.id }), + primaryAuthor: (r) => JSON.stringify({ id: r?.id }), + primaryTitle: (r) => JSON.stringify({ id: r?.id }), }; - const PostsIdSet = new Set( - Array.isArray(Posts) - ? Posts.map((r) => getIDValue.Posts?.(r)) - : getIDValue.Posts?.(Posts) + const primaryAuthorIdSet = new Set( + Array.isArray(primaryAuthor) + ? primaryAuthor.map((r) => getIDValue.primaryAuthor?.(r)) + : getIDValue.primaryAuthor?.(primaryAuthor) + ); + const primaryTitleIdSet = new Set( + Array.isArray(primaryTitle) + ? primaryTitle.map((r) => getIDValue.primaryTitle?.(r)) + : getIDValue.primaryTitle?.(primaryTitle) ); const getDisplayValue = { - Posts: (r) => r?.title, - statuses: (r) => { - const enumDisplayValueMap = { - PENDING: \\"Pending\\", - POSTED: \\"Posted\\", - IN_REVIEW: \\"In review\\", - }; - return enumDisplayValueMap[r]; - }, + primaryAuthor: (r) => r?.name, + primaryTitle: (r) => r?.name, }; const validations = { - label: [], - Posts: [], - statuses: [], + name: [], + primaryAuthor: [], + primaryTitle: [], }; const runValidationTasks = async ( fieldName, @@ -2642,32 +3817,59 @@ export default function TagCreateForm(props) { setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; - const fetchPostsRecords = async (value) => { - setPostsLoading(true); + const fetchPrimaryAuthorRecords = async (value) => { + setPrimaryAuthorLoading(true); const newOptions = []; let newNext = \\"\\"; while (newOptions.length < autocompleteLength && newNext != null) { const variables = { limit: autocompleteLength * 5, - filter: { or: [{ title: { contains: value } }] }, + filter: { or: [{ name: { contains: value } }] }, }; if (newNext) { variables[\\"nextToken\\"] = newNext; } const result = ( await API.graphql({ - query: listPosts, + query: listAuthors, variables, }) - ).data.listPosts.item; + ).data.listPrimaryAuthors.item; var loaded = result.filter( - (item) => !PostsIdSet.has(getIDValue.Posts?.(item)) + (item) => !primaryAuthorIdSet.has(getIDValue.primaryAuthor?.(item)) ); newOptions.push(...loaded); newNext = result.nextToken; } - setPostsRecords(newOptions.slice(0, autocompleteLength)); - setPostsLoading(false); + setPrimaryAuthorRecords(newOptions.slice(0, autocompleteLength)); + setPrimaryAuthorLoading(false); + }; + const fetchPrimaryTitleRecords = async (value) => { + setPrimaryTitleLoading(true); + const newOptions = []; + let newNext = \\"\\"; + while (newOptions.length < autocompleteLength && newNext != null) { + const variables = { + limit: autocompleteLength * 5, + filter: { or: [{ name: { contains: value } }] }, + }; + if (newNext) { + variables[\\"nextToken\\"] = newNext; + } + const result = ( + await API.graphql({ + query: listTitles, + variables, + }) + ).data.listPrimaryTitles.item; + var loaded = result.filter( + (item) => !primaryTitleIdSet.has(getIDValue.primaryTitle?.(item)) + ); + newOptions.push(...loaded); + newNext = result.nextToken; + } + setPrimaryTitleRecords(newOptions.slice(0, autocompleteLength)); + setPrimaryTitleLoading(false); }; return ( { event.preventDefault(); let modelFields = { - label, - Posts, - statuses, + name, + primaryAuthor, + primaryTitle, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { @@ -2718,36 +3920,14 @@ export default function TagCreateForm(props) { modelFields[key] = undefined; } }); - const modelFieldsToSave = { - label: modelFields.label, - statuses: modelFields.statuses, - }; - const tag = await API.graphql({ - query: createTag, + await API.graphql({ + query: createBook, variables: { input: { - ...modelFieldsToSave, + ...modelFields, }, }, }); - const promises = []; - promises.push( - ...Posts.reduce((promises, post) => { - promises.push( - API.graphql({ - query: createTagPost, - variables: { - input: { - tag, - post, - }, - }, - }) - ); - return promises; - }, []) - ); - await Promise.all(promises); if (onSuccess) { onSuccess(modelFields); } @@ -2760,252 +3940,279 @@ export default function TagCreateForm(props) { } } }} - {...getOverrideProps(overrides, \\"TagCreateForm\\")} + {...getOverrideProps(overrides, \\"BookCreateForm\\")} {...rest} > + + + + + + + { let { value } = e.target; if (onChange) { const modelFields = { - label: value, - Posts, - statuses, + name: value, + primaryAuthor, + primaryTitle, }; const result = onChange(modelFields); - value = result?.label ?? value; + value = result?.name ?? value; } - if (errors.label?.hasError) { - runValidationTasks(\\"label\\", value); + if (errors.name?.hasError) { + runValidationTasks(\\"name\\", value); } - setLabel(value); + setName(value); }} - onBlur={() => runValidationTasks(\\"label\\", label)} - errorMessage={errors.label?.errorMessage} - hasError={errors.label?.hasError} - {...getOverrideProps(overrides, \\"label\\")} + onBlur={() => runValidationTasks(\\"name\\", name)} + errorMessage={errors.name?.errorMessage} + hasError={errors.name?.hasError} + {...getOverrideProps(overrides, \\"name\\")} > { - let values = items; + let value = items[0]; if (onChange) { const modelFields = { - label, - Posts: values, - statuses, + name, + primaryAuthor: value, + primaryTitle, }; const result = onChange(modelFields); - values = result?.Posts ?? values; + value = result?.primaryAuthor ?? value; } - setPosts(values); - setCurrentPostsValue(undefined); - setCurrentPostsDisplayValue(\\"\\"); + setPrimaryAuthor(value); + setCurrentPrimaryAuthorValue(undefined); + setCurrentPrimaryAuthorDisplayValue(\\"\\"); }} - currentFieldValue={currentPostsValue} - label={\\"Posts\\"} - items={Posts} - hasError={errors?.Posts?.hasError} - errorMessage={errors?.Posts?.errorMessage} - getBadgeText={getDisplayValue.Posts} + currentFieldValue={currentPrimaryAuthorValue} + label={\\"Primary author\\"} + items={primaryAuthor ? [primaryAuthor] : []} + hasError={errors?.primaryAuthor?.hasError} + errorMessage={errors?.primaryAuthor?.errorMessage} + getBadgeText={getDisplayValue.primaryAuthor} setFieldValue={(model) => { - setCurrentPostsDisplayValue( - model ? getDisplayValue.Posts(model) : \\"\\" + setCurrentPrimaryAuthorDisplayValue( + model ? getDisplayValue.primaryAuthor(model) : \\"\\" ); - setCurrentPostsValue(model); + setCurrentPrimaryAuthorValue(model); }} - inputFieldRef={PostsRef} + inputFieldRef={primaryAuthorRef} defaultFieldValue={\\"\\"} > ({ - id: getIDValue.Posts?.(r), - label: getDisplayValue.Posts?.(r), - }))} - isLoading={PostsLoading} + placeholder=\\"Search Author\\" + value={currentPrimaryAuthorDisplayValue} + options={primaryAuthorRecords + .filter( + (r) => !primaryAuthorIdSet.has(getIDValue.primaryAuthor?.(r)) + ) + .map((r) => ({ + id: getIDValue.primaryAuthor?.(r), + label: getDisplayValue.primaryAuthor?.(r), + }))} + isLoading={primaryAuthorLoading} onSelect={({ id, label }) => { - setCurrentPostsValue( - postRecords.find((r) => + setCurrentPrimaryAuthorValue( + authorRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); - setCurrentPostsDisplayValue(label); - runValidationTasks(\\"Posts\\", label); + setCurrentPrimaryAuthorDisplayValue(label); + runValidationTasks(\\"primaryAuthor\\", label); }} onClear={() => { - setCurrentPostsDisplayValue(\\"\\"); + setCurrentPrimaryAuthorDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; - fetchPostsRecords(value); - if (errors.Posts?.hasError) { - runValidationTasks(\\"Posts\\", value); + fetchPrimaryAuthorRecords(value); + if (errors.primaryAuthor?.hasError) { + runValidationTasks(\\"primaryAuthor\\", value); } - setCurrentPostsDisplayValue(value); - setCurrentPostsValue(undefined); + setCurrentPrimaryAuthorDisplayValue(value); + setCurrentPrimaryAuthorValue(undefined); }} - onBlur={() => runValidationTasks(\\"Posts\\", currentPostsDisplayValue)} - errorMessage={errors.Posts?.errorMessage} - hasError={errors.Posts?.hasError} - ref={PostsRef} + onBlur={() => + runValidationTasks( + \\"primaryAuthor\\", + currentPrimaryAuthorDisplayValue + ) + } + errorMessage={errors.primaryAuthor?.errorMessage} + hasError={errors.primaryAuthor?.hasError} + ref={primaryAuthorRef} labelHidden={true} - {...getOverrideProps(overrides, \\"Posts\\")} + {...getOverrideProps(overrides, \\"primaryAuthor\\")} > { - let values = items; + let value = items[0]; if (onChange) { const modelFields = { - label, - Posts, - statuses: values, + name, + primaryAuthor, + primaryTitle: value, }; const result = onChange(modelFields); - values = result?.statuses ?? values; + value = result?.primaryTitle ?? value; } - setStatuses(values); - setCurrentStatusesValue(\\"\\"); + setPrimaryTitle(value); + setCurrentPrimaryTitleValue(undefined); + setCurrentPrimaryTitleDisplayValue(\\"\\"); }} - currentFieldValue={currentStatusesValue} - label={\\"Statuses\\"} - items={statuses} - hasError={errors?.statuses?.hasError} - errorMessage={errors?.statuses?.errorMessage} - getBadgeText={getDisplayValue.statuses} - setFieldValue={setCurrentStatusesValue} - inputFieldRef={statusesRef} + currentFieldValue={currentPrimaryTitleValue} + label={\\"Primary title\\"} + items={primaryTitle ? [primaryTitle] : []} + hasError={errors?.primaryTitle?.hasError} + errorMessage={errors?.primaryTitle?.errorMessage} + getBadgeText={getDisplayValue.primaryTitle} + setFieldValue={(model) => { + setCurrentPrimaryTitleDisplayValue( + model ? getDisplayValue.primaryTitle(model) : \\"\\" + ); + setCurrentPrimaryTitleValue(model); + }} + inputFieldRef={primaryTitleRef} defaultFieldValue={\\"\\"} > - { - let { value } = e.target; - if (errors.statuses?.hasError) { - runValidationTasks(\\"statuses\\", value); - } - setCurrentStatusesValue(value); - }} - onBlur={() => runValidationTasks(\\"statuses\\", currentStatusesValue)} - errorMessage={errors.statuses?.errorMessage} - hasError={errors.statuses?.hasError} - ref={statusesRef} - labelHidden={true} - {...getOverrideProps(overrides, \\"statuses\\")} - > - - - - - - - - - - - - + onClear={() => { + setCurrentPrimaryTitleDisplayValue(\\"\\"); + }} + onChange={(e) => { + let { value } = e.target; + fetchPrimaryTitleRecords(value); + if (errors.primaryTitle?.hasError) { + runValidationTasks(\\"primaryTitle\\", value); + } + setCurrentPrimaryTitleDisplayValue(value); + setCurrentPrimaryTitleValue(undefined); + }} + onBlur={() => + runValidationTasks(\\"primaryTitle\\", currentPrimaryTitleDisplayValue) + } + errorMessage={errors.primaryTitle?.errorMessage} + hasError={errors.primaryTitle?.hasError} + ref={primaryTitleRef} + labelHidden={true} + {...getOverrideProps(overrides, \\"primaryTitle\\")} + > + ); } " `; -exports[`amplify form renderer tests GraphQL form tests should generate a create form with manyToMany relationship 2`] = ` +exports[`amplify form renderer tests GraphQL form tests should generate a create form with multiple hasOne relationships 2`] = ` "import * as React from \\"react\\"; -import { AutocompleteProps, GridProps, SelectFieldProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; +import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; -import { Post } from \\"../API\\"; +import { Author, Title } from \\"../API\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise; -export declare type TagCreateFormInputValues = { - label?: string; - Posts?: Post[]; - statuses?: string[]; +export declare type BookCreateFormInputValues = { + name?: string; + primaryAuthor?: Author; + primaryTitle?: Title; }; -export declare type TagCreateFormValidationValues = { - label?: ValidationFunction; - Posts?: ValidationFunction; - statuses?: ValidationFunction; +export declare type BookCreateFormValidationValues = { + name?: ValidationFunction; + primaryAuthor?: ValidationFunction; + primaryTitle?: ValidationFunction; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; -export declare type TagCreateFormOverridesProps = { - TagCreateFormGrid?: PrimitiveOverrideProps<GridProps>; - label?: PrimitiveOverrideProps<TextFieldProps>; - Posts?: PrimitiveOverrideProps<AutocompleteProps>; - statuses?: PrimitiveOverrideProps<SelectFieldProps>; +export declare type BookCreateFormOverridesProps = { + BookCreateFormGrid?: PrimitiveOverrideProps<GridProps>; + name?: PrimitiveOverrideProps<TextFieldProps>; + primaryAuthor?: PrimitiveOverrideProps<AutocompleteProps>; + primaryTitle?: PrimitiveOverrideProps<AutocompleteProps>; } & EscapeHatchProps; -export declare type TagCreateFormProps = React.PropsWithChildren<{ - overrides?: TagCreateFormOverridesProps | undefined | null; +export declare type BookCreateFormProps = React.PropsWithChildren<{ + overrides?: BookCreateFormOverridesProps | undefined | null; } & { clearOnSuccess?: boolean; - onSubmit?: (fields: TagCreateFormInputValues) => TagCreateFormInputValues; - onSuccess?: (fields: TagCreateFormInputValues) => void; - onError?: (fields: TagCreateFormInputValues, errorMessage: string) => void; + onSubmit?: (fields: BookCreateFormInputValues) => BookCreateFormInputValues; + onSuccess?: (fields: BookCreateFormInputValues) => void; + onError?: (fields: BookCreateFormInputValues, errorMessage: string) => void; onCancel?: () => void; - onChange?: (fields: TagCreateFormInputValues) => TagCreateFormInputValues; - onValidate?: TagCreateFormValidationValues; + onChange?: (fields: BookCreateFormInputValues) => BookCreateFormInputValues; + onValidate?: BookCreateFormValidationValues; } & React.CSSProperties>; -export default function TagCreateForm(props: TagCreateFormProps): React.ReactElement; +export default function BookCreateForm(props: BookCreateFormProps): React.ReactElement; " `; -exports[`amplify form renderer tests GraphQL form tests should generate a create form with multiple hasOne relationships 1`] = ` +exports[`amplify form renderer tests GraphQL form tests should generate a relationship update form with autocomplete 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { @@ -3024,8 +4231,8 @@ import { import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { API } from \\"aws-amplify\\"; -import { listAuthors, listTitles } from \\"../graphql/queries\\"; -import { createBook } from \\"../graphql/mutations\\"; +import { commentsByPostID, getPost, listComments } from \\"../graphql/queries\\"; +import { deleteComment, updateComment, updatePost } from \\"../graphql/mutations\\"; function ArrayField({ items = [], onChange, @@ -3184,151 +4391,165 @@ function ArrayField({ </React.Fragment> ); } -export default function BookCreateForm(props) { +export default function PostUpdateForm(props) { const { - clearOnSuccess = true, + id: idProp, + post: postModelProp, onSuccess, onError, onSubmit, - onCancel, onValidate, onChange, overrides, ...rest } = props; const initialValues = { - name: \\"\\", - primaryAuthor: undefined, - primaryTitle: undefined, + title: \\"\\", + body: \\"\\", + publishDate: \\"\\", + Comments: [], }; - const [name, setName] = React.useState(initialValues.name); - const [primaryAuthor, setPrimaryAuthor] = React.useState( - initialValues.primaryAuthor - ); - const [primaryAuthorLoading, setPrimaryAuthorLoading] = React.useState(false); - const [primaryAuthorRecords, setPrimaryAuthorRecords] = React.useState([]); - const [primaryTitle, setPrimaryTitle] = React.useState( - initialValues.primaryTitle + const [title, setTitle] = React.useState(initialValues.title); + const [body, setBody] = React.useState(initialValues.body); + const [publishDate, setPublishDate] = React.useState( + initialValues.publishDate ); - const [primaryTitleLoading, setPrimaryTitleLoading] = React.useState(false); - const [primaryTitleRecords, setPrimaryTitleRecords] = React.useState([]); + const [Comments, setComments] = React.useState(initialValues.Comments); + const [CommentsLoading, setCommentsLoading] = React.useState(false); + const [CommentsRecords, setCommentsRecords] = React.useState([]); const autocompleteLength = 10; const [errors, setErrors] = React.useState({}); const resetStateValues = () => { - setName(initialValues.name); - setPrimaryAuthor(initialValues.primaryAuthor); - setCurrentPrimaryAuthorValue(undefined); - setCurrentPrimaryAuthorDisplayValue(\\"\\"); - setPrimaryTitle(initialValues.primaryTitle); - setCurrentPrimaryTitleValue(undefined); - setCurrentPrimaryTitleDisplayValue(\\"\\"); + const cleanValues = postRecord + ? { ...initialValues, ...postRecord, Comments: linkedComments } + : initialValues; + setTitle(cleanValues.title); + setBody(cleanValues.body); + setPublishDate(cleanValues.publishDate); + setComments(cleanValues.Comments ?? []); + setCurrentCommentsValue(undefined); + setCurrentCommentsDisplayValue(\\"\\"); setErrors({}); }; - const [ - currentPrimaryAuthorDisplayValue, - setCurrentPrimaryAuthorDisplayValue, - ] = React.useState(\\"\\"); - const [currentPrimaryAuthorValue, setCurrentPrimaryAuthorValue] = - React.useState(undefined); - const primaryAuthorRef = React.createRef(); - const [currentPrimaryTitleDisplayValue, setCurrentPrimaryTitleDisplayValue] = + const [postRecord, setPostRecord] = React.useState(postModelProp); + const [linkedComments, setLinkedComments] = React.useState([]); + const canUnlinkComments = false; + React.useEffect(() => { + const queryData = async () => { + const record = idProp + ? ( + await API.graphql({ + query: getPost, + variables: { + input: { + id: idProp, + }, + }, + }) + ).data.getPost + : postModelProp; + setPostRecord(record); + const linkedComments = record + ? ( + await API.graphql({ + query: commentsByPostID, + variables: { + input: { + postID: record.id, + }, + }, + }) + ).data.commentsByPostID.items + : []; + setLinkedComments(linkedComments); + }; + queryData(); + }, [idProp, postModelProp]); + React.useEffect(resetStateValues, [postRecord, linkedComments]); + const [currentCommentsDisplayValue, setCurrentCommentsDisplayValue] = React.useState(\\"\\"); - const [currentPrimaryTitleValue, setCurrentPrimaryTitleValue] = + const [currentCommentsValue, setCurrentCommentsValue] = React.useState(undefined); - const primaryTitleRef = React.createRef(); + const CommentsRef = React.createRef(); const getIDValue = { - primaryAuthor: (r) => JSON.stringify({ id: r?.id }), - primaryTitle: (r) => JSON.stringify({ id: r?.id }), + Comments: (r) => JSON.stringify({ id: r?.id }), }; - const primaryAuthorIdSet = new Set( - Array.isArray(primaryAuthor) - ? primaryAuthor.map((r) => getIDValue.primaryAuthor?.(r)) - : getIDValue.primaryAuthor?.(primaryAuthor) - ); - const primaryTitleIdSet = new Set( - Array.isArray(primaryTitle) - ? primaryTitle.map((r) => getIDValue.primaryTitle?.(r)) - : getIDValue.primaryTitle?.(primaryTitle) + const CommentsIdSet = new Set( + Array.isArray(Comments) + ? Comments.map((r) => getIDValue.Comments?.(r)) + : getIDValue.Comments?.(Comments) ); const getDisplayValue = { - primaryAuthor: (r) => r?.name, - primaryTitle: (r) => r?.name, + Comments: (r) => \`\${r?.body ? r?.body + \\" - \\" : \\"\\"}\${r?.id}\`, }; const validations = { - name: [], - primaryAuthor: [], - primaryTitle: [], - }; - const runValidationTasks = async ( - fieldName, - currentValue, - getDisplayValue - ) => { - const value = - currentValue && getDisplayValue - ? getDisplayValue(currentValue) - : currentValue; - let validationResponse = validateField(value, validations[fieldName]); - const customValidator = fetchByPath(onValidate, fieldName); - if (customValidator) { - validationResponse = await customValidator(value, validationResponse); - } - setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); - return validationResponse; - }; - const fetchPrimaryAuthorRecords = async (value) => { - setPrimaryAuthorLoading(true); - const newOptions = []; - let newNext = \\"\\"; - while (newOptions.length < autocompleteLength && newNext != null) { - const variables = { - limit: autocompleteLength * 5, - filter: { or: [{ name: { contains: value } }] }, - }; - if (newNext) { - variables[\\"nextToken\\"] = newNext; - } - const result = ( - await API.graphql({ - query: listAuthors, - variables, - }) - ).data.listPrimaryAuthors.item; - var loaded = result.filter( - (item) => !primaryAuthorIdSet.has(getIDValue.primaryAuthor?.(item)) - ); - newOptions.push(...loaded); - newNext = result.nextToken; + title: [], + body: [], + publishDate: [], + Comments: [], + }; + const runValidationTasks = async ( + fieldName, + currentValue, + getDisplayValue + ) => { + const value = + currentValue && getDisplayValue + ? getDisplayValue(currentValue) + : currentValue; + let validationResponse = validateField(value, validations[fieldName]); + const customValidator = fetchByPath(onValidate, fieldName); + if (customValidator) { + validationResponse = await customValidator(value, validationResponse); } - setPrimaryAuthorRecords(newOptions.slice(0, autocompleteLength)); - setPrimaryAuthorLoading(false); + setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); + return validationResponse; }; - const fetchPrimaryTitleRecords = async (value) => { - setPrimaryTitleLoading(true); + const convertToLocal = (date) => { + const df = new Intl.DateTimeFormat(\\"default\\", { + year: \\"numeric\\", + month: \\"2-digit\\", + day: \\"2-digit\\", + hour: \\"2-digit\\", + minute: \\"2-digit\\", + calendar: \\"iso8601\\", + numberingSystem: \\"latn\\", + hourCycle: \\"h23\\", + }); + const parts = df.formatToParts(date).reduce((acc, part) => { + acc[part.type] = part.value; + return acc; + }, {}); + return \`\${parts.year}-\${parts.month}-\${parts.day}T\${parts.hour}:\${parts.minute}\`; + }; + const fetchCommentsRecords = async (value) => { + setCommentsLoading(true); const newOptions = []; let newNext = \\"\\"; while (newOptions.length < autocompleteLength && newNext != null) { const variables = { limit: autocompleteLength * 5, - filter: { or: [{ name: { contains: value } }] }, + filter: { + or: [{ body: { contains: value } }, { id: { contains: value } }], + }, }; if (newNext) { variables[\\"nextToken\\"] = newNext; } const result = ( await API.graphql({ - query: listTitles, + query: listComments, variables, }) - ).data.listPrimaryTitles.item; + ).data.listComments.item; var loaded = result.filter( - (item) => !primaryTitleIdSet.has(getIDValue.primaryTitle?.(item)) + (item) => !CommentsIdSet.has(getIDValue.Comments?.(item)) ); newOptions.push(...loaded); newNext = result.nextToken; } - setPrimaryTitleRecords(newOptions.slice(0, autocompleteLength)); - setPrimaryTitleLoading(false); + setCommentsRecords(newOptions.slice(0, autocompleteLength)); + setCommentsLoading(false); }; return ( <Grid @@ -3339,9 +4560,10 @@ export default function BookCreateForm(props) { onSubmit={async (event) => { event.preventDefault(); let modelFields = { - name, - primaryAuthor, - primaryTitle, + title, + body, + publishDate, + Comments, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { @@ -3379,303 +4601,328 @@ export default function BookCreateForm(props) { modelFields[key] = undefined; } }); - await API.graphql({ - query: createBook, - variables: { - input: { - ...modelFields, - }, - }, + const promises = []; + const commentsToLink = []; + const commentsToUnLink = []; + const commentsSet = new Set(); + const linkedCommentsSet = new Set(); + Comments.forEach((r) => commentsSet.add(getIDValue.Comments?.(r))); + linkedComments.forEach((r) => + linkedCommentsSet.add(getIDValue.Comments?.(r)) + ); + linkedComments.forEach((r) => { + if (!commentsSet.has(getIDValue.Comments?.(r))) { + commentsToUnLink.push(r); + } + }); + Comments.forEach((r) => { + if (!linkedCommentsSet.has(getIDValue.Comments?.(r))) { + commentsToLink.push(r); + } + }); + commentsToUnLink.forEach((original) => { + if (!canUnlinkComments) { + throw Error( + \`Comment \${original.id} cannot be unlinked from Post because postID is a required field.\` + ); + } + promises.push( + API.graphql({ + query: deleteComment, + variables: { + input: { + id: original.id, + }, + }, + }) + ); + }); + commentsToLink.forEach((original) => { + promises.push( + API.graphql({ + query: updateComment, + variables: { + input: { + id: original.id, + postID: postRecord.id, + }, + }, + }) + ); }); + const modelFieldsToSave = { + title: modelFields.title, + body: modelFields.body, + publishDate: modelFields.publishDate, + }; + promises.push( + API.graphql({ + query: updatePost, + variables: { + input: { + id: postRecord.id, + ...modelFieldsToSave, + }, + }, + }) + ); + await Promise.all(promises); if (onSuccess) { onSuccess(modelFields); } - if (clearOnSuccess) { - resetStateValues(); - } } catch (err) { if (onError) { onError(modelFields, err.message); } } }} - {...getOverrideProps(overrides, \\"BookCreateForm\\")} + {...getOverrideProps(overrides, \\"PostUpdateForm\\")} {...rest} > - <Flex - justifyContent=\\"space-between\\" - {...getOverrideProps(overrides, \\"CTAFlex\\")} - > - <Button - children=\\"Clear\\" - type=\\"reset\\" - onClick={(event) => { - event.preventDefault(); - resetStateValues(); - }} - {...getOverrideProps(overrides, \\"ClearButton\\")} - ></Button> - <Flex - gap=\\"15px\\" - {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} - > - <Button - children=\\"Cancel\\" - type=\\"button\\" - onClick={() => { - onCancel && onCancel(); - }} - {...getOverrideProps(overrides, \\"CancelButton\\")} - ></Button> - <Button - children=\\"Submit\\" - type=\\"submit\\" - variation=\\"primary\\" - isDisabled={Object.values(errors).some((e) => e?.hasError)} - {...getOverrideProps(overrides, \\"SubmitButton\\")} - ></Button> - </Flex> - </Flex> <TextField - label=\\"Name\\" + label=\\"Title\\" isRequired={false} isReadOnly={false} - value={name} + value={title} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { - name: value, - primaryAuthor, - primaryTitle, + title: value, + body, + publishDate, + Comments, }; const result = onChange(modelFields); - value = result?.name ?? value; + value = result?.title ?? value; } - if (errors.name?.hasError) { - runValidationTasks(\\"name\\", value); + if (errors.title?.hasError) { + runValidationTasks(\\"title\\", value); } - setName(value); + setTitle(value); }} - onBlur={() => runValidationTasks(\\"name\\", name)} - errorMessage={errors.name?.errorMessage} - hasError={errors.name?.hasError} - {...getOverrideProps(overrides, \\"name\\")} + onBlur={() => runValidationTasks(\\"title\\", title)} + errorMessage={errors.title?.errorMessage} + hasError={errors.title?.hasError} + {...getOverrideProps(overrides, \\"title\\")} + ></TextField> + <TextField + label=\\"Body\\" + isRequired={false} + isReadOnly={false} + value={body} + onChange={(e) => { + let { value } = e.target; + if (onChange) { + const modelFields = { + title, + body: value, + publishDate, + Comments, + }; + const result = onChange(modelFields); + value = result?.body ?? value; + } + if (errors.body?.hasError) { + runValidationTasks(\\"body\\", value); + } + setBody(value); + }} + onBlur={() => runValidationTasks(\\"body\\", body)} + errorMessage={errors.body?.errorMessage} + hasError={errors.body?.hasError} + {...getOverrideProps(overrides, \\"body\\")} + ></TextField> + <TextField + label=\\"Publish date\\" + isRequired={false} + isReadOnly={false} + type=\\"datetime-local\\" + value={publishDate && convertToLocal(new Date(publishDate))} + onChange={(e) => { + let value = + e.target.value === \\"\\" ? \\"\\" : new Date(e.target.value).toISOString(); + if (onChange) { + const modelFields = { + title, + body, + publishDate: value, + Comments, + }; + const result = onChange(modelFields); + value = result?.publishDate ?? value; + } + if (errors.publishDate?.hasError) { + runValidationTasks(\\"publishDate\\", value); + } + setPublishDate(value); + }} + onBlur={() => runValidationTasks(\\"publishDate\\", publishDate)} + errorMessage={errors.publishDate?.errorMessage} + hasError={errors.publishDate?.hasError} + {...getOverrideProps(overrides, \\"publishDate\\")} ></TextField> <ArrayField - lengthLimit={1} onChange={async (items) => { - let value = items[0]; + let values = items; if (onChange) { const modelFields = { - name, - primaryAuthor: value, - primaryTitle, + title, + body, + publishDate, + Comments: values, }; const result = onChange(modelFields); - value = result?.primaryAuthor ?? value; + values = result?.Comments ?? values; } - setPrimaryAuthor(value); - setCurrentPrimaryAuthorValue(undefined); - setCurrentPrimaryAuthorDisplayValue(\\"\\"); + setComments(values); + setCurrentCommentsValue(undefined); + setCurrentCommentsDisplayValue(\\"\\"); }} - currentFieldValue={currentPrimaryAuthorValue} - label={\\"Primary author\\"} - items={primaryAuthor ? [primaryAuthor] : []} - hasError={errors?.primaryAuthor?.hasError} - errorMessage={errors?.primaryAuthor?.errorMessage} - getBadgeText={getDisplayValue.primaryAuthor} + currentFieldValue={currentCommentsValue} + label={\\"Comments\\"} + items={Comments} + hasError={errors?.Comments?.hasError} + errorMessage={errors?.Comments?.errorMessage} + getBadgeText={getDisplayValue.Comments} setFieldValue={(model) => { - setCurrentPrimaryAuthorDisplayValue( - model ? getDisplayValue.primaryAuthor(model) : \\"\\" + setCurrentCommentsDisplayValue( + model ? getDisplayValue.Comments(model) : \\"\\" ); - setCurrentPrimaryAuthorValue(model); + setCurrentCommentsValue(model); }} - inputFieldRef={primaryAuthorRef} + inputFieldRef={CommentsRef} defaultFieldValue={\\"\\"} > <Autocomplete - label=\\"Primary author\\" + label=\\"Comments\\" isRequired={false} isReadOnly={false} - placeholder=\\"Search Author\\" - value={currentPrimaryAuthorDisplayValue} - options={primaryAuthorRecords - .filter( - (r) => !primaryAuthorIdSet.has(getIDValue.primaryAuthor?.(r)) - ) - .map((r) => ({ - id: getIDValue.primaryAuthor?.(r), - label: getDisplayValue.primaryAuthor?.(r), - }))} - isLoading={primaryAuthorLoading} + placeholder=\\"Search Comment\\" + value={currentCommentsDisplayValue} + options={commentsRecords.map((r) => ({ + id: getIDValue.Comments?.(r), + label: getDisplayValue.Comments?.(r), + }))} + isLoading={CommentsLoading} onSelect={({ id, label }) => { - setCurrentPrimaryAuthorValue( - authorRecords.find((r) => + setCurrentCommentsValue( + commentRecords.find((r) => Object.entries(JSON.parse(id)).every( ([key, value]) => r[key] === value ) ) ); - setCurrentPrimaryAuthorDisplayValue(label); - runValidationTasks(\\"primaryAuthor\\", label); + setCurrentCommentsDisplayValue(label); + runValidationTasks(\\"Comments\\", label); }} onClear={() => { - setCurrentPrimaryAuthorDisplayValue(\\"\\"); + setCurrentCommentsDisplayValue(\\"\\"); }} onChange={(e) => { let { value } = e.target; - fetchPrimaryAuthorRecords(value); - if (errors.primaryAuthor?.hasError) { - runValidationTasks(\\"primaryAuthor\\", value); + fetchCommentsRecords(value); + if (errors.Comments?.hasError) { + runValidationTasks(\\"Comments\\", value); } - setCurrentPrimaryAuthorDisplayValue(value); - setCurrentPrimaryAuthorValue(undefined); + setCurrentCommentsDisplayValue(value); + setCurrentCommentsValue(undefined); }} onBlur={() => - runValidationTasks( - \\"primaryAuthor\\", - currentPrimaryAuthorDisplayValue - ) + runValidationTasks(\\"Comments\\", currentCommentsDisplayValue) } - errorMessage={errors.primaryAuthor?.errorMessage} - hasError={errors.primaryAuthor?.hasError} - ref={primaryAuthorRef} + errorMessage={errors.Comments?.errorMessage} + hasError={errors.Comments?.hasError} + ref={CommentsRef} labelHidden={true} - {...getOverrideProps(overrides, \\"primaryAuthor\\")} + {...getOverrideProps(overrides, \\"Comments\\")} ></Autocomplete> </ArrayField> - <ArrayField - lengthLimit={1} - onChange={async (items) => { - let value = items[0]; - if (onChange) { - const modelFields = { - name, - primaryAuthor, - primaryTitle: value, - }; - const result = onChange(modelFields); - value = result?.primaryTitle ?? value; - } - setPrimaryTitle(value); - setCurrentPrimaryTitleValue(undefined); - setCurrentPrimaryTitleDisplayValue(\\"\\"); - }} - currentFieldValue={currentPrimaryTitleValue} - label={\\"Primary title\\"} - items={primaryTitle ? [primaryTitle] : []} - hasError={errors?.primaryTitle?.hasError} - errorMessage={errors?.primaryTitle?.errorMessage} - getBadgeText={getDisplayValue.primaryTitle} - setFieldValue={(model) => { - setCurrentPrimaryTitleDisplayValue( - model ? getDisplayValue.primaryTitle(model) : \\"\\" - ); - setCurrentPrimaryTitleValue(model); - }} - inputFieldRef={primaryTitleRef} - defaultFieldValue={\\"\\"} + <Flex + justifyContent=\\"space-between\\" + {...getOverrideProps(overrides, \\"CTAFlex\\")} > - <Autocomplete - label=\\"Primary title\\" - isRequired={false} - isReadOnly={false} - placeholder=\\"Search Title\\" - value={currentPrimaryTitleDisplayValue} - options={primaryTitleRecords - .filter((r) => !primaryTitleIdSet.has(getIDValue.primaryTitle?.(r))) - .map((r) => ({ - id: getIDValue.primaryTitle?.(r), - label: getDisplayValue.primaryTitle?.(r), - }))} - isLoading={primaryTitleLoading} - onSelect={({ id, label }) => { - setCurrentPrimaryTitleValue( - titleRecords.find((r) => - Object.entries(JSON.parse(id)).every( - ([key, value]) => r[key] === value - ) - ) - ); - setCurrentPrimaryTitleDisplayValue(label); - runValidationTasks(\\"primaryTitle\\", label); - }} - onClear={() => { - setCurrentPrimaryTitleDisplayValue(\\"\\"); + <Button + children=\\"Reset\\" + type=\\"reset\\" + onClick={(event) => { + event.preventDefault(); + resetStateValues(); }} - onChange={(e) => { - let { value } = e.target; - fetchPrimaryTitleRecords(value); - if (errors.primaryTitle?.hasError) { - runValidationTasks(\\"primaryTitle\\", value); + isDisabled={!(idProp || postModelProp)} + {...getOverrideProps(overrides, \\"ResetButton\\")} + ></Button> + <Flex + gap=\\"15px\\" + {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} + > + <Button + children=\\"Submit\\" + type=\\"submit\\" + variation=\\"primary\\" + isDisabled={ + !(idProp || postModelProp) || + Object.values(errors).some((e) => e?.hasError) } - setCurrentPrimaryTitleDisplayValue(value); - setCurrentPrimaryTitleValue(undefined); - }} - onBlur={() => - runValidationTasks(\\"primaryTitle\\", currentPrimaryTitleDisplayValue) - } - errorMessage={errors.primaryTitle?.errorMessage} - hasError={errors.primaryTitle?.hasError} - ref={primaryTitleRef} - labelHidden={true} - {...getOverrideProps(overrides, \\"primaryTitle\\")} - ></Autocomplete> - </ArrayField> + {...getOverrideProps(overrides, \\"SubmitButton\\")} + ></Button> + </Flex> + </Flex> </Grid> ); } " `; -exports[`amplify form renderer tests GraphQL form tests should generate a create form with multiple hasOne relationships 2`] = ` +exports[`amplify form renderer tests GraphQL form tests should generate a relationship update form with autocomplete 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; -import { Author, Title } from \\"../API\\"; +import { Comment, Post } from \\"../API\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; -export declare type BookCreateFormInputValues = { - name?: string; - primaryAuthor?: Author; - primaryTitle?: Title; +export declare type PostUpdateFormInputValues = { + title?: string; + body?: string; + publishDate?: string; + Comments?: Comment[]; }; -export declare type BookCreateFormValidationValues = { - name?: ValidationFunction<string>; - primaryAuthor?: ValidationFunction<Author>; - primaryTitle?: ValidationFunction<Title>; +export declare type PostUpdateFormValidationValues = { + title?: ValidationFunction<string>; + body?: ValidationFunction<string>; + publishDate?: ValidationFunction<string>; + Comments?: ValidationFunction<Comment>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; -export declare type BookCreateFormOverridesProps = { - BookCreateFormGrid?: PrimitiveOverrideProps<GridProps>; - name?: PrimitiveOverrideProps<TextFieldProps>; - primaryAuthor?: PrimitiveOverrideProps<AutocompleteProps>; - primaryTitle?: PrimitiveOverrideProps<AutocompleteProps>; +export declare type PostUpdateFormOverridesProps = { + PostUpdateFormGrid?: PrimitiveOverrideProps<GridProps>; + title?: PrimitiveOverrideProps<TextFieldProps>; + body?: PrimitiveOverrideProps<TextFieldProps>; + publishDate?: PrimitiveOverrideProps<TextFieldProps>; + Comments?: PrimitiveOverrideProps<AutocompleteProps>; } & EscapeHatchProps; -export declare type BookCreateFormProps = React.PropsWithChildren<{ - overrides?: BookCreateFormOverridesProps | undefined | null; +export declare type PostUpdateFormProps = React.PropsWithChildren<{ + overrides?: PostUpdateFormOverridesProps | undefined | null; } & { - clearOnSuccess?: boolean; - onSubmit?: (fields: BookCreateFormInputValues) => BookCreateFormInputValues; - onSuccess?: (fields: BookCreateFormInputValues) => void; - onError?: (fields: BookCreateFormInputValues, errorMessage: string) => void; - onCancel?: () => void; - onChange?: (fields: BookCreateFormInputValues) => BookCreateFormInputValues; - onValidate?: BookCreateFormValidationValues; + id?: string; + post?: Post; + onSubmit?: (fields: PostUpdateFormInputValues) => PostUpdateFormInputValues; + onSuccess?: (fields: PostUpdateFormInputValues) => void; + onError?: (fields: PostUpdateFormInputValues, errorMessage: string) => void; + onChange?: (fields: PostUpdateFormInputValues) => PostUpdateFormInputValues; + onValidate?: PostUpdateFormValidationValues; } & React.CSSProperties>; -export default function BookCreateForm(props: BookCreateFormProps): React.ReactElement; +export default function PostUpdateForm(props: PostUpdateFormProps): React.ReactElement; " `; -exports[`amplify form renderer tests GraphQL form tests should generate a relationship update form with autocomplete 1`] = ` +exports[`amplify form renderer tests GraphQL form tests should generate a update form without relationships 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { - Autocomplete, Badge, Button, Divider, @@ -3684,14 +4931,15 @@ import { Icon, ScrollView, Text, + TextAreaField, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { API } from \\"aws-amplify\\"; -import { commentsByPostID, getPost, listComments } from \\"../graphql/queries\\"; -import { deleteComment, updateComment, updatePost } from \\"../graphql/mutations\\"; +import { getPost } from \\"../graphql/queries\\"; +import { updatePost } from \\"../graphql/mutations\\"; function ArrayField({ items = [], onChange, @@ -3850,49 +5098,74 @@ function ArrayField({ </React.Fragment> ); } -export default function PostUpdateForm(props) { +export default function MyPostForm(props) { const { id: idProp, post: postModelProp, onSuccess, onError, onSubmit, + onCancel, onValidate, onChange, overrides, ...rest } = props; const initialValues = { - title: \\"\\", - body: \\"\\", - publishDate: \\"\\", - Comments: [], + TextAreaFieldbbd63464: \\"\\", + caption: \\"\\", + username: \\"\\", + profile_url: \\"\\", + post_url: \\"\\", + metadata: \\"\\", + nonModelField: \\"\\", + nonModelFieldArray: [], }; - const [title, setTitle] = React.useState(initialValues.title); - const [body, setBody] = React.useState(initialValues.body); - const [publishDate, setPublishDate] = React.useState( - initialValues.publishDate + const [TextAreaFieldbbd63464, setTextAreaFieldbbd63464] = React.useState( + initialValues.TextAreaFieldbbd63464 + ); + const [caption, setCaption] = React.useState(initialValues.caption); + const [username, setUsername] = React.useState(initialValues.username); + const [profile_url, setProfile_url] = React.useState( + initialValues.profile_url + ); + const [post_url, setPost_url] = React.useState(initialValues.post_url); + const [metadata, setMetadata] = React.useState(initialValues.metadata); + const [nonModelField, setNonModelField] = React.useState( + initialValues.nonModelField + ); + const [nonModelFieldArray, setNonModelFieldArray] = React.useState( + initialValues.nonModelFieldArray ); - const [Comments, setComments] = React.useState(initialValues.Comments); - const [CommentsLoading, setCommentsLoading] = React.useState(false); - const [CommentsRecords, setCommentsRecords] = React.useState([]); - const autocompleteLength = 10; const [errors, setErrors] = React.useState({}); const resetStateValues = () => { const cleanValues = postRecord - ? { ...initialValues, ...postRecord, Comments: linkedComments } + ? { ...initialValues, ...postRecord } : initialValues; - setTitle(cleanValues.title); - setBody(cleanValues.body); - setPublishDate(cleanValues.publishDate); - setComments(cleanValues.Comments ?? []); - setCurrentCommentsValue(undefined); - setCurrentCommentsDisplayValue(\\"\\"); + setTextAreaFieldbbd63464(cleanValues.TextAreaFieldbbd63464); + setCaption(cleanValues.caption); + setUsername(cleanValues.username); + setProfile_url(cleanValues.profile_url); + setPost_url(cleanValues.post_url); + setMetadata( + typeof cleanValues.metadata === \\"string\\" + ? cleanValues.metadata + : JSON.stringify(cleanValues.metadata) + ); + setNonModelField( + typeof cleanValues.nonModelField === \\"string\\" + ? cleanValues.nonModelField + : JSON.stringify(cleanValues.nonModelField) + ); + setNonModelFieldArray( + cleanValues.nonModelFieldArray?.map((item) => + typeof item === \\"string\\" ? item : JSON.stringify(item) + ) ?? [] + ); + setCurrentNonModelFieldArrayValue(\\"\\"); setErrors({}); }; const [postRecord, setPostRecord] = React.useState(postModelProp); - const [linkedComments, setLinkedComments] = React.useState([]); - const canUnlinkComments = false; React.useEffect(() => { const queryData = async () => { const record = idProp @@ -3908,44 +5181,22 @@ export default function PostUpdateForm(props) { ).data.getPost : postModelProp; setPostRecord(record); - const linkedComments = record - ? ( - await API.graphql({ - query: commentsByPostID, - variables: { - input: { - postID: record.id, - }, - }, - }) - ).data.commentsByPostID.items - : []; - setLinkedComments(linkedComments); }; queryData(); }, [idProp, postModelProp]); - React.useEffect(resetStateValues, [postRecord, linkedComments]); - const [currentCommentsDisplayValue, setCurrentCommentsDisplayValue] = + React.useEffect(resetStateValues, [postRecord]); + const [currentNonModelFieldArrayValue, setCurrentNonModelFieldArrayValue] = React.useState(\\"\\"); - const [currentCommentsValue, setCurrentCommentsValue] = - React.useState(undefined); - const CommentsRef = React.createRef(); - const getIDValue = { - Comments: (r) => JSON.stringify({ id: r?.id }), - }; - const CommentsIdSet = new Set( - Array.isArray(Comments) - ? Comments.map((r) => getIDValue.Comments?.(r)) - : getIDValue.Comments?.(Comments) - ); - const getDisplayValue = { - Comments: (r) => \`\${r?.body ? r?.body + \\" - \\" : \\"\\"}\${r?.id}\`, - }; + const nonModelFieldArrayRef = React.createRef(); const validations = { - title: [], - body: [], - publishDate: [], - Comments: [], + TextAreaFieldbbd63464: [], + caption: [], + username: [], + profile_url: [{ type: \\"URL\\" }], + post_url: [{ type: \\"URL\\" }], + metadata: [{ type: \\"JSON\\" }], + nonModelField: [{ type: \\"JSON\\" }], + nonModelFieldArray: [{ type: \\"JSON\\" }], }; const runValidationTasks = async ( fieldName, @@ -3964,52 +5215,6 @@ export default function PostUpdateForm(props) { setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; - const convertToLocal = (date) => { - const df = new Intl.DateTimeFormat(\\"default\\", { - year: \\"numeric\\", - month: \\"2-digit\\", - day: \\"2-digit\\", - hour: \\"2-digit\\", - minute: \\"2-digit\\", - calendar: \\"iso8601\\", - numberingSystem: \\"latn\\", - hourCycle: \\"h23\\", - }); - const parts = df.formatToParts(date).reduce((acc, part) => { - acc[part.type] = part.value; - return acc; - }, {}); - return \`\${parts.year}-\${parts.month}-\${parts.day}T\${parts.hour}:\${parts.minute}\`; - }; - const fetchCommentsRecords = async (value) => { - setCommentsLoading(true); - const newOptions = []; - let newNext = \\"\\"; - while (newOptions.length < autocompleteLength && newNext != null) { - const variables = { - limit: autocompleteLength * 5, - filter: { - or: [{ body: { contains: value } }, { id: { contains: value } }], - }, - }; - if (newNext) { - variables[\\"nextToken\\"] = newNext; - } - const result = ( - await API.graphql({ - query: listComments, - variables, - }) - ).data.listComments.item; - var loaded = result.filter( - (item) => !CommentsIdSet.has(getIDValue.Comments?.(item)) - ); - newOptions.push(...loaded); - newNext = result.nextToken; - } - setCommentsRecords(newOptions.slice(0, autocompleteLength)); - setCommentsLoading(false); - }; return ( <Grid as=\\"form\\" @@ -4019,31 +5224,27 @@ export default function PostUpdateForm(props) { onSubmit={async (event) => { event.preventDefault(); let modelFields = { - title, - body, - publishDate, - Comments, + TextAreaFieldbbd63464, + caption, + username, + profile_url, + post_url, + metadata, + nonModelField, + nonModelFieldArray, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => - runValidationTasks( - fieldName, - item, - getDisplayValue[fieldName] - ) + runValidationTasks(fieldName, item) ) ); return promises; } promises.push( - runValidationTasks( - fieldName, - modelFields[fieldName], - getDisplayValue[fieldName] - ) + runValidationTasks(fieldName, modelFields[fieldName]) ); return promises; }, []) @@ -4060,72 +5261,28 @@ export default function PostUpdateForm(props) { modelFields[key] = undefined; } }); - const promises = []; - const commentsToLink = []; - const commentsToUnLink = []; - const commentsSet = new Set(); - const linkedCommentsSet = new Set(); - Comments.forEach((r) => commentsSet.add(getIDValue.Comments?.(r))); - linkedComments.forEach((r) => - linkedCommentsSet.add(getIDValue.Comments?.(r)) - ); - linkedComments.forEach((r) => { - if (!commentsSet.has(getIDValue.Comments?.(r))) { - commentsToUnLink.push(r); - } - }); - Comments.forEach((r) => { - if (!linkedCommentsSet.has(getIDValue.Comments?.(r))) { - commentsToLink.push(r); - } - }); - commentsToUnLink.forEach((original) => { - if (!canUnlinkComments) { - throw Error( - \`Comment \${original.id} cannot be unlinked from Post because postID is a required field.\` - ); - } - promises.push( - API.graphql({ - query: deleteComment, - variables: { - input: { - id: original.id, - }, - }, - }) - ); - }); - commentsToLink.forEach((original) => { - promises.push( - API.graphql({ - query: updateComment, - variables: { - input: { - id: original.id, - postID: postRecord.id, - }, - }, - }) - ); - }); const modelFieldsToSave = { - title: modelFields.title, - body: modelFields.body, - publishDate: modelFields.publishDate, + caption: modelFields.caption, + username: modelFields.username, + profile_url: modelFields.profile_url, + post_url: modelFields.post_url, + metadata: modelFields.metadata, + nonModelFieldArray: modelFields.nonModelFieldArray.map((s) => + JSON.parse(s) + ), + nonModelField: modelFields.nonModelField + ? JSON.parse(modelFields.nonModelField) + : modelFields.nonModelField, }; - promises.push( - API.graphql({ - query: updatePost, - variables: { - input: { - id: postRecord.id, - ...modelFieldsToSave, - }, + await API.graphql({ + query: updatePost, + variables: { + input: { + id: postRecord.id, + ...modelFieldsToSave, }, - }) - ); - await Promise.all(promises); + }, + }); if (onSuccess) { onSuccess(modelFields); } @@ -4135,167 +5292,317 @@ export default function PostUpdateForm(props) { } } }} - {...getOverrideProps(overrides, \\"PostUpdateForm\\")} + {...getOverrideProps(overrides, \\"MyPostForm\\")} {...rest} > + <Flex + justifyContent=\\"space-between\\" + {...getOverrideProps(overrides, \\"CTAFlex\\")} + > + <Button + children=\\"Reset\\" + type=\\"reset\\" + onClick={(event) => { + event.preventDefault(); + resetStateValues(); + }} + isDisabled={!(idProp || postModelProp)} + {...getOverrideProps(overrides, \\"ResetButton\\")} + ></Button> + <Flex + gap=\\"15px\\" + {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} + > + <Button + children=\\"Cancel\\" + type=\\"button\\" + onClick={() => { + onCancel && onCancel(); + }} + {...getOverrideProps(overrides, \\"CancelButton\\")} + ></Button> + <Button + children=\\"Submit\\" + type=\\"submit\\" + variation=\\"primary\\" + isDisabled={ + !(idProp || postModelProp) || + Object.values(errors).some((e) => e?.hasError) + } + {...getOverrideProps(overrides, \\"SubmitButton\\")} + ></Button> + </Flex> + </Flex> + <TextAreaField + label=\\"Label\\" + value={TextAreaFieldbbd63464} + onChange={(e) => { + let { value } = e.target; + if (onChange) { + const modelFields = { + TextAreaFieldbbd63464: value, + caption, + username, + profile_url, + post_url, + metadata, + nonModelField, + nonModelFieldArray, + }; + const result = onChange(modelFields); + value = result?.TextAreaFieldbbd63464 ?? value; + } + if (errors.TextAreaFieldbbd63464?.hasError) { + runValidationTasks(\\"TextAreaFieldbbd63464\\", value); + } + setTextAreaFieldbbd63464(value); + }} + onBlur={() => + runValidationTasks(\\"TextAreaFieldbbd63464\\", TextAreaFieldbbd63464) + } + errorMessage={errors.TextAreaFieldbbd63464?.errorMessage} + hasError={errors.TextAreaFieldbbd63464?.hasError} + {...getOverrideProps(overrides, \\"TextAreaFieldbbd63464\\")} + ></TextAreaField> <TextField - label=\\"Title\\" + label=\\"Caption\\" isRequired={false} isReadOnly={false} - value={title} + value={caption} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { - title: value, - body, - publishDate, - Comments, + TextAreaFieldbbd63464, + caption: value, + username, + profile_url, + post_url, + metadata, + nonModelField, + nonModelFieldArray, }; const result = onChange(modelFields); - value = result?.title ?? value; + value = result?.caption ?? value; } - if (errors.title?.hasError) { - runValidationTasks(\\"title\\", value); + if (errors.caption?.hasError) { + runValidationTasks(\\"caption\\", value); } - setTitle(value); + setCaption(value); }} - onBlur={() => runValidationTasks(\\"title\\", title)} - errorMessage={errors.title?.errorMessage} - hasError={errors.title?.hasError} - {...getOverrideProps(overrides, \\"title\\")} + onBlur={() => runValidationTasks(\\"caption\\", caption)} + errorMessage={errors.caption?.errorMessage} + hasError={errors.caption?.hasError} + {...getOverrideProps(overrides, \\"caption\\")} ></TextField> <TextField - label=\\"Body\\" + label=\\"Username\\" isRequired={false} isReadOnly={false} - value={body} + value={username} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { - title, - body: value, - publishDate, - Comments, + TextAreaFieldbbd63464, + caption, + username: value, + profile_url, + post_url, + metadata, + nonModelField, + nonModelFieldArray, }; const result = onChange(modelFields); - value = result?.body ?? value; + value = result?.username ?? value; } - if (errors.body?.hasError) { - runValidationTasks(\\"body\\", value); + if (errors.username?.hasError) { + runValidationTasks(\\"username\\", value); } - setBody(value); + setUsername(value); }} - onBlur={() => runValidationTasks(\\"body\\", body)} - errorMessage={errors.body?.errorMessage} - hasError={errors.body?.hasError} - {...getOverrideProps(overrides, \\"body\\")} + onBlur={() => runValidationTasks(\\"username\\", username)} + errorMessage={errors.username?.errorMessage} + hasError={errors.username?.hasError} + {...getOverrideProps(overrides, \\"username\\")} ></TextField> <TextField - label=\\"Publish date\\" + label=\\"Profile url\\" isRequired={false} isReadOnly={false} - type=\\"datetime-local\\" - value={publishDate && convertToLocal(new Date(publishDate))} + value={profile_url} onChange={(e) => { - let value = - e.target.value === \\"\\" ? \\"\\" : new Date(e.target.value).toISOString(); + let { value } = e.target; if (onChange) { const modelFields = { - title, - body, - publishDate: value, - Comments, + TextAreaFieldbbd63464, + caption, + username, + profile_url: value, + post_url, + metadata, + nonModelField, + nonModelFieldArray, + }; + const result = onChange(modelFields); + value = result?.profile_url ?? value; + } + if (errors.profile_url?.hasError) { + runValidationTasks(\\"profile_url\\", value); + } + setProfile_url(value); + }} + onBlur={() => runValidationTasks(\\"profile_url\\", profile_url)} + errorMessage={errors.profile_url?.errorMessage} + hasError={errors.profile_url?.hasError} + {...getOverrideProps(overrides, \\"profile_url\\")} + ></TextField> + <TextField + label=\\"Post url\\" + isRequired={false} + isReadOnly={false} + value={post_url} + onChange={(e) => { + let { value } = e.target; + if (onChange) { + const modelFields = { + TextAreaFieldbbd63464, + caption, + username, + profile_url, + post_url: value, + metadata, + nonModelField, + nonModelFieldArray, + }; + const result = onChange(modelFields); + value = result?.post_url ?? value; + } + if (errors.post_url?.hasError) { + runValidationTasks(\\"post_url\\", value); + } + setPost_url(value); + }} + onBlur={() => runValidationTasks(\\"post_url\\", post_url)} + errorMessage={errors.post_url?.errorMessage} + hasError={errors.post_url?.hasError} + {...getOverrideProps(overrides, \\"post_url\\")} + ></TextField> + <TextAreaField + label=\\"Metadata\\" + isRequired={false} + isReadOnly={false} + value={metadata} + onChange={(e) => { + let { value } = e.target; + if (onChange) { + const modelFields = { + TextAreaFieldbbd63464, + caption, + username, + profile_url, + post_url, + metadata: value, + nonModelField, + nonModelFieldArray, + }; + const result = onChange(modelFields); + value = result?.metadata ?? value; + } + if (errors.metadata?.hasError) { + runValidationTasks(\\"metadata\\", value); + } + setMetadata(value); + }} + onBlur={() => runValidationTasks(\\"metadata\\", metadata)} + errorMessage={errors.metadata?.errorMessage} + hasError={errors.metadata?.hasError} + {...getOverrideProps(overrides, \\"metadata\\")} + ></TextAreaField> + <TextAreaField + label=\\"Non model field\\" + isRequired={false} + isReadOnly={false} + value={nonModelField} + onChange={(e) => { + let { value } = e.target; + if (onChange) { + const modelFields = { + TextAreaFieldbbd63464, + caption, + username, + profile_url, + post_url, + metadata, + nonModelField: value, + nonModelFieldArray, }; const result = onChange(modelFields); - value = result?.publishDate ?? value; + value = result?.nonModelField ?? value; } - if (errors.publishDate?.hasError) { - runValidationTasks(\\"publishDate\\", value); + if (errors.nonModelField?.hasError) { + runValidationTasks(\\"nonModelField\\", value); } - setPublishDate(value); + setNonModelField(value); }} - onBlur={() => runValidationTasks(\\"publishDate\\", publishDate)} - errorMessage={errors.publishDate?.errorMessage} - hasError={errors.publishDate?.hasError} - {...getOverrideProps(overrides, \\"publishDate\\")} - ></TextField> + onBlur={() => runValidationTasks(\\"nonModelField\\", nonModelField)} + errorMessage={errors.nonModelField?.errorMessage} + hasError={errors.nonModelField?.hasError} + {...getOverrideProps(overrides, \\"nonModelField\\")} + ></TextAreaField> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { - title, - body, - publishDate, - Comments: values, + TextAreaFieldbbd63464, + caption, + username, + profile_url, + post_url, + metadata, + nonModelField, + nonModelFieldArray: values, }; const result = onChange(modelFields); - values = result?.Comments ?? values; + values = result?.nonModelFieldArray ?? values; } - setComments(values); - setCurrentCommentsValue(undefined); - setCurrentCommentsDisplayValue(\\"\\"); - }} - currentFieldValue={currentCommentsValue} - label={\\"Comments\\"} - items={Comments} - hasError={errors?.Comments?.hasError} - errorMessage={errors?.Comments?.errorMessage} - getBadgeText={getDisplayValue.Comments} - setFieldValue={(model) => { - setCurrentCommentsDisplayValue( - model ? getDisplayValue.Comments(model) : \\"\\" - ); - setCurrentCommentsValue(model); + setNonModelFieldArray(values); + setCurrentNonModelFieldArrayValue(\\"\\"); }} - inputFieldRef={CommentsRef} + currentFieldValue={currentNonModelFieldArrayValue} + label={\\"Non model field array\\"} + items={nonModelFieldArray} + hasError={errors?.nonModelFieldArray?.hasError} + errorMessage={errors?.nonModelFieldArray?.errorMessage} + setFieldValue={setCurrentNonModelFieldArrayValue} + inputFieldRef={nonModelFieldArrayRef} defaultFieldValue={\\"\\"} > - <Autocomplete - label=\\"Comments\\" + <TextAreaField + label=\\"Non model field array\\" isRequired={false} isReadOnly={false} - placeholder=\\"Search Comment\\" - value={currentCommentsDisplayValue} - options={commentsRecords.map((r) => ({ - id: getIDValue.Comments?.(r), - label: getDisplayValue.Comments?.(r), - }))} - isLoading={CommentsLoading} - onSelect={({ id, label }) => { - setCurrentCommentsValue( - commentRecords.find((r) => - Object.entries(JSON.parse(id)).every( - ([key, value]) => r[key] === value - ) - ) - ); - setCurrentCommentsDisplayValue(label); - runValidationTasks(\\"Comments\\", label); - }} - onClear={() => { - setCurrentCommentsDisplayValue(\\"\\"); - }} + value={currentNonModelFieldArrayValue} onChange={(e) => { let { value } = e.target; - fetchCommentsRecords(value); - if (errors.Comments?.hasError) { - runValidationTasks(\\"Comments\\", value); + if (errors.nonModelFieldArray?.hasError) { + runValidationTasks(\\"nonModelFieldArray\\", value); } - setCurrentCommentsDisplayValue(value); - setCurrentCommentsValue(undefined); + setCurrentNonModelFieldArrayValue(value); }} onBlur={() => - runValidationTasks(\\"Comments\\", currentCommentsDisplayValue) + runValidationTasks( + \\"nonModelFieldArray\\", + currentNonModelFieldArrayValue + ) } - errorMessage={errors.Comments?.errorMessage} - hasError={errors.Comments?.hasError} - ref={CommentsRef} + errorMessage={errors.nonModelFieldArray?.errorMessage} + hasError={errors.nonModelFieldArray?.hasError} + ref={nonModelFieldArrayRef} labelHidden={true} - {...getOverrideProps(overrides, \\"Comments\\")} - ></Autocomplete> + {...getOverrideProps(overrides, \\"nonModelFieldArray\\")} + ></TextAreaField> </ArrayField> <Flex justifyContent=\\"space-between\\" @@ -4315,6 +5622,14 @@ export default function PostUpdateForm(props) { gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > + <Button + children=\\"Cancel\\" + type=\\"button\\" + onClick={() => { + onCancel && onCancel(); + }} + {...getOverrideProps(overrides, \\"CancelButton\\")} + ></Button> <Button children=\\"Submit\\" type=\\"submit\\" @@ -4333,55 +5648,69 @@ export default function PostUpdateForm(props) { " `; -exports[`amplify form renderer tests GraphQL form tests should generate a relationship update form with autocomplete 2`] = ` +exports[`amplify form renderer tests GraphQL form tests should generate a update form without relationships 2`] = ` "import * as React from \\"react\\"; -import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; +import { GridProps, TextAreaFieldProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; -import { Comment, Post } from \\"../API\\"; +import { Post } from \\"../API\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; -export declare type PostUpdateFormInputValues = { - title?: string; - body?: string; - publishDate?: string; - Comments?: Comment[]; +export declare type MyPostFormInputValues = { + TextAreaFieldbbd63464?: string; + caption?: string; + username?: string; + profile_url?: string; + post_url?: string; + metadata?: string; + nonModelField?: string; + nonModelFieldArray?: string[]; }; -export declare type PostUpdateFormValidationValues = { - title?: ValidationFunction<string>; - body?: ValidationFunction<string>; - publishDate?: ValidationFunction<string>; - Comments?: ValidationFunction<Comment>; +export declare type MyPostFormValidationValues = { + TextAreaFieldbbd63464?: ValidationFunction<string>; + caption?: ValidationFunction<string>; + username?: ValidationFunction<string>; + profile_url?: ValidationFunction<string>; + post_url?: ValidationFunction<string>; + metadata?: ValidationFunction<string>; + nonModelField?: ValidationFunction<string>; + nonModelFieldArray?: ValidationFunction<string>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; -export declare type PostUpdateFormOverridesProps = { - PostUpdateFormGrid?: PrimitiveOverrideProps<GridProps>; - title?: PrimitiveOverrideProps<TextFieldProps>; - body?: PrimitiveOverrideProps<TextFieldProps>; - publishDate?: PrimitiveOverrideProps<TextFieldProps>; - Comments?: PrimitiveOverrideProps<AutocompleteProps>; +export declare type MyPostFormOverridesProps = { + MyPostFormGrid?: PrimitiveOverrideProps<GridProps>; + TextAreaFieldbbd63464?: PrimitiveOverrideProps<TextAreaFieldProps>; + caption?: PrimitiveOverrideProps<TextFieldProps>; + username?: PrimitiveOverrideProps<TextFieldProps>; + profile_url?: PrimitiveOverrideProps<TextFieldProps>; + post_url?: PrimitiveOverrideProps<TextFieldProps>; + metadata?: PrimitiveOverrideProps<TextAreaFieldProps>; + nonModelField?: PrimitiveOverrideProps<TextAreaFieldProps>; + nonModelFieldArray?: PrimitiveOverrideProps<TextAreaFieldProps>; } & EscapeHatchProps; -export declare type PostUpdateFormProps = React.PropsWithChildren<{ - overrides?: PostUpdateFormOverridesProps | undefined | null; +export declare type MyPostFormProps = React.PropsWithChildren<{ + overrides?: MyPostFormOverridesProps | undefined | null; } & { id?: string; post?: Post; - onSubmit?: (fields: PostUpdateFormInputValues) => PostUpdateFormInputValues; - onSuccess?: (fields: PostUpdateFormInputValues) => void; - onError?: (fields: PostUpdateFormInputValues, errorMessage: string) => void; - onChange?: (fields: PostUpdateFormInputValues) => PostUpdateFormInputValues; - onValidate?: PostUpdateFormValidationValues; + onSubmit?: (fields: MyPostFormInputValues) => MyPostFormInputValues; + onSuccess?: (fields: MyPostFormInputValues) => void; + onError?: (fields: MyPostFormInputValues, errorMessage: string) => void; + onCancel?: () => void; + onChange?: (fields: MyPostFormInputValues) => MyPostFormInputValues; + onValidate?: MyPostFormValidationValues; } & React.CSSProperties>; -export default function PostUpdateForm(props: PostUpdateFormProps): React.ReactElement; +export default function MyPostForm(props: MyPostFormProps): React.ReactElement; " `; -exports[`amplify form renderer tests GraphQL form tests should generate a update form without relationships 1`] = ` +exports[`amplify form renderer tests GraphQL form tests should generate an update form with hasMany relationship 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { + Autocomplete, Badge, Button, Divider, @@ -4390,15 +5719,14 @@ import { Icon, ScrollView, Text, - TextAreaField, TextField, useTheme, } from \\"@aws-amplify/ui-react\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { API } from \\"aws-amplify\\"; -import { getPost } from \\"../graphql/queries\\"; -import { updatePost } from \\"../graphql/mutations\\"; +import { getComment, listPosts } from \\"../graphql/queries\\"; +import { updateComment } from \\"../graphql/mutations\\"; function ArrayField({ items = [], onChange, @@ -4557,105 +5885,97 @@ function ArrayField({ </React.Fragment> ); } -export default function MyPostForm(props) { +export default function CommentUpdateForm(props) { const { id: idProp, - post: postModelProp, + comment: commentModelProp, onSuccess, onError, onSubmit, - onCancel, onValidate, onChange, overrides, ...rest } = props; const initialValues = { - TextAreaFieldbbd63464: \\"\\", - caption: \\"\\", - username: \\"\\", - profile_url: \\"\\", - post_url: \\"\\", - metadata: \\"\\", - nonModelField: \\"\\", - nonModelFieldArray: [], + content: \\"\\", + postID: undefined, + Post: undefined, + post: \\"\\", }; - const [TextAreaFieldbbd63464, setTextAreaFieldbbd63464] = React.useState( - initialValues.TextAreaFieldbbd63464 - ); - const [caption, setCaption] = React.useState(initialValues.caption); - const [username, setUsername] = React.useState(initialValues.username); - const [profile_url, setProfile_url] = React.useState( - initialValues.profile_url - ); - const [post_url, setPost_url] = React.useState(initialValues.post_url); - const [metadata, setMetadata] = React.useState(initialValues.metadata); - const [nonModelField, setNonModelField] = React.useState( - initialValues.nonModelField - ); - const [nonModelFieldArray, setNonModelFieldArray] = React.useState( - initialValues.nonModelFieldArray - ); + const [content, setContent] = React.useState(initialValues.content); + const [postID, setPostID] = React.useState(initialValues.postID); + const [postIDLoading, setPostIDLoading] = React.useState(false); + const [postIDRecords, setPostIDRecords] = React.useState([]); + const [Post, setPost] = React.useState(initialValues.Post); + const [PostLoading, setPostLoading] = React.useState(false); + const [PostRecords, setPostRecords] = React.useState([]); + const [post1, setPost1] = React.useState(initialValues.post); + const autocompleteLength = 10; const [errors, setErrors] = React.useState({}); const resetStateValues = () => { - const cleanValues = postRecord - ? { ...initialValues, ...postRecord } - : initialValues; - setTextAreaFieldbbd63464(cleanValues.TextAreaFieldbbd63464); - setCaption(cleanValues.caption); - setUsername(cleanValues.username); - setProfile_url(cleanValues.profile_url); - setPost_url(cleanValues.post_url); - setMetadata( - typeof cleanValues.metadata === \\"string\\" - ? cleanValues.metadata - : JSON.stringify(cleanValues.metadata) - ); - setNonModelField( - typeof cleanValues.nonModelField === \\"string\\" - ? cleanValues.nonModelField - : JSON.stringify(cleanValues.nonModelField) - ); - setNonModelFieldArray( - cleanValues.nonModelFieldArray?.map((item) => - typeof item === \\"string\\" ? item : JSON.stringify(item) - ) ?? [] - ); - setCurrentNonModelFieldArrayValue(\\"\\"); + const cleanValues = commentRecord + ? { ...initialValues, ...commentRecord, postID, Post } + : initialValues; + setContent(cleanValues.content); + setPostID(cleanValues.postID); + setCurrentPostIDValue(undefined); + setCurrentPostIDDisplayValue(\\"\\"); + setPost(cleanValues.Post); + setCurrentPostValue(undefined); + setCurrentPostDisplayValue(\\"\\"); + setPost1(cleanValues.post); setErrors({}); }; - const [postRecord, setPostRecord] = React.useState(postModelProp); + const [commentRecord, setCommentRecord] = React.useState(commentModelProp); React.useEffect(() => { const queryData = async () => { const record = idProp ? ( await API.graphql({ - query: getPost, + query: getComment, variables: { input: { id: idProp, }, }, }) - ).data.getPost - : postModelProp; - setPostRecord(record); + ).data.getComment + : commentModelProp; + setCommentRecord(record); + const postIDRecord = record ? await record.postID : undefined; + setPostID(postIDRecord); + const PostRecord = record ? await record.Post : undefined; + setPost(PostRecord); }; queryData(); - }, [idProp, postModelProp]); - React.useEffect(resetStateValues, [postRecord]); - const [currentNonModelFieldArrayValue, setCurrentNonModelFieldArrayValue] = + }, [idProp, commentModelProp]); + React.useEffect(resetStateValues, [commentRecord, postID, Post]); + const [currentPostIDDisplayValue, setCurrentPostIDDisplayValue] = React.useState(\\"\\"); - const nonModelFieldArrayRef = React.createRef(); + const [currentPostIDValue, setCurrentPostIDValue] = React.useState(undefined); + const postIDRef = React.createRef(); + const [currentPostDisplayValue, setCurrentPostDisplayValue] = + React.useState(\\"\\"); + const [currentPostValue, setCurrentPostValue] = React.useState(undefined); + const PostRef = React.createRef(); + const getIDValue = { + Post: (r) => JSON.stringify({ id: r?.id }), + }; + const PostIdSet = new Set( + Array.isArray(Post) + ? Post.map((r) => getIDValue.Post?.(r)) + : getIDValue.Post?.(Post) + ); + const getDisplayValue = { + postID: (r) => \`\${r?.title ? r?.title + \\" - \\" : \\"\\"}\${r?.id}\`, + Post: (r) => \`\${r?.title ? r?.title + \\" - \\" : \\"\\"}\${r?.id}\`, + }; const validations = { - TextAreaFieldbbd63464: [], - caption: [], - username: [], - profile_url: [{ type: \\"URL\\" }], - post_url: [{ type: \\"URL\\" }], - metadata: [{ type: \\"JSON\\" }], - nonModelField: [{ type: \\"JSON\\" }], - nonModelFieldArray: [{ type: \\"JSON\\" }], + content: [], + postID: [{ type: \\"Required\\" }], + Post: [], + post: [], }; const runValidationTasks = async ( fieldName, @@ -4674,6 +5994,64 @@ export default function MyPostForm(props) { setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; + const fetchPostIDRecords = async (value) => { + setPostIDLoading(true); + const newOptions = []; + let newNext = \\"\\"; + while (newOptions.length < autocompleteLength && newNext != null) { + const variables = { + limit: autocompleteLength * 5, + filter: { + or: [{ title: { contains: value } }, { id: { contains: value } }], + }, + }; + if (newNext) { + variables[\\"nextToken\\"] = newNext; + } + const result = ( + await API.graphql({ + query: listPosts, + variables, + }) + ).data.listPostIDS.item; + var loaded = result.filter( + (item) => !postIDIdSet.has(getIDValue.postID?.(item)) + ); + newOptions.push(...loaded); + newNext = result.nextToken; + } + setPostIDRecords(newOptions.slice(0, autocompleteLength)); + setPostIDLoading(false); + }; + const fetchPostRecords = async (value) => { + setPostLoading(true); + const newOptions = []; + let newNext = \\"\\"; + while (newOptions.length < autocompleteLength && newNext != null) { + const variables = { + limit: autocompleteLength * 5, + filter: { + or: [{ title: { contains: value } }, { id: { contains: value } }], + }, + }; + if (newNext) { + variables[\\"nextToken\\"] = newNext; + } + const result = ( + await API.graphql({ + query: listPosts, + variables, + }) + ).data.listPosts.item; + var loaded = result.filter( + (item) => !PostIdSet.has(getIDValue.Post?.(item)) + ); + newOptions.push(...loaded); + newNext = result.nextToken; + } + setPostRecords(newOptions.slice(0, autocompleteLength)); + setPostLoading(false); + }; return ( <Grid as=\\"form\\" @@ -4683,386 +6061,281 @@ export default function MyPostForm(props) { onSubmit={async (event) => { event.preventDefault(); let modelFields = { - TextAreaFieldbbd63464, - caption, - username, - profile_url, - post_url, - metadata, - nonModelField, - nonModelFieldArray, + content, + postID, + Post, + post: post1, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => - runValidationTasks(fieldName, item) + runValidationTasks( + fieldName, + item, + getDisplayValue[fieldName] + ) ) ); - return promises; - } - promises.push( - runValidationTasks(fieldName, modelFields[fieldName]) - ); - return promises; - }, []) - ); - if (validationResponses.some((r) => r.hasError)) { - return; - } - if (onSubmit) { - modelFields = onSubmit(modelFields); - } - try { - Object.entries(modelFields).forEach(([key, value]) => { - if (typeof value === \\"string\\" && value.trim() === \\"\\") { - modelFields[key] = undefined; - } - }); - const modelFieldsToSave = { - caption: modelFields.caption, - username: modelFields.username, - profile_url: modelFields.profile_url, - post_url: modelFields.post_url, - metadata: modelFields.metadata, - nonModelFieldArray: modelFields.nonModelFieldArray.map((s) => - JSON.parse(s) - ), - nonModelField: modelFields.nonModelField - ? JSON.parse(modelFields.nonModelField) - : modelFields.nonModelField, - }; - await API.graphql({ - query: updatePost, - variables: { - input: { - id: postRecord.id, - ...modelFieldsToSave, - }, - }, - }); - if (onSuccess) { - onSuccess(modelFields); - } - } catch (err) { - if (onError) { - onError(modelFields, err.message); - } - } - }} - {...getOverrideProps(overrides, \\"MyPostForm\\")} - {...rest} - > - <Flex - justifyContent=\\"space-between\\" - {...getOverrideProps(overrides, \\"CTAFlex\\")} - > - <Button - children=\\"Reset\\" - type=\\"reset\\" - onClick={(event) => { - event.preventDefault(); - resetStateValues(); - }} - isDisabled={!(idProp || postModelProp)} - {...getOverrideProps(overrides, \\"ResetButton\\")} - ></Button> - <Flex - gap=\\"15px\\" - {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} - > - <Button - children=\\"Cancel\\" - type=\\"button\\" - onClick={() => { - onCancel && onCancel(); - }} - {...getOverrideProps(overrides, \\"CancelButton\\")} - ></Button> - <Button - children=\\"Submit\\" - type=\\"submit\\" - variation=\\"primary\\" - isDisabled={ - !(idProp || postModelProp) || - Object.values(errors).some((e) => e?.hasError) - } - {...getOverrideProps(overrides, \\"SubmitButton\\")} - ></Button> - </Flex> - </Flex> - <TextAreaField - label=\\"Label\\" - value={TextAreaFieldbbd63464} - onChange={(e) => { - let { value } = e.target; - if (onChange) { - const modelFields = { - TextAreaFieldbbd63464: value, - caption, - username, - profile_url, - post_url, - metadata, - nonModelField, - nonModelFieldArray, - }; - const result = onChange(modelFields); - value = result?.TextAreaFieldbbd63464 ?? value; - } - if (errors.TextAreaFieldbbd63464?.hasError) { - runValidationTasks(\\"TextAreaFieldbbd63464\\", value); - } - setTextAreaFieldbbd63464(value); - }} - onBlur={() => - runValidationTasks(\\"TextAreaFieldbbd63464\\", TextAreaFieldbbd63464) + return promises; + } + promises.push( + runValidationTasks( + fieldName, + modelFields[fieldName], + getDisplayValue[fieldName] + ) + ); + return promises; + }, []) + ); + if (validationResponses.some((r) => r.hasError)) { + return; } - errorMessage={errors.TextAreaFieldbbd63464?.errorMessage} - hasError={errors.TextAreaFieldbbd63464?.hasError} - {...getOverrideProps(overrides, \\"TextAreaFieldbbd63464\\")} - ></TextAreaField> - <TextField - label=\\"Caption\\" - isRequired={false} - isReadOnly={false} - value={caption} - onChange={(e) => { - let { value } = e.target; - if (onChange) { - const modelFields = { - TextAreaFieldbbd63464, - caption: value, - username, - profile_url, - post_url, - metadata, - nonModelField, - nonModelFieldArray, - }; - const result = onChange(modelFields); - value = result?.caption ?? value; - } - if (errors.caption?.hasError) { - runValidationTasks(\\"caption\\", value); - } - setCaption(value); - }} - onBlur={() => runValidationTasks(\\"caption\\", caption)} - errorMessage={errors.caption?.errorMessage} - hasError={errors.caption?.hasError} - {...getOverrideProps(overrides, \\"caption\\")} - ></TextField> - <TextField - label=\\"Username\\" - isRequired={false} - isReadOnly={false} - value={username} - onChange={(e) => { - let { value } = e.target; - if (onChange) { - const modelFields = { - TextAreaFieldbbd63464, - caption, - username: value, - profile_url, - post_url, - metadata, - nonModelField, - nonModelFieldArray, - }; - const result = onChange(modelFields); - value = result?.username ?? value; - } - if (errors.username?.hasError) { - runValidationTasks(\\"username\\", value); - } - setUsername(value); - }} - onBlur={() => runValidationTasks(\\"username\\", username)} - errorMessage={errors.username?.errorMessage} - hasError={errors.username?.hasError} - {...getOverrideProps(overrides, \\"username\\")} - ></TextField> - <TextField - label=\\"Profile url\\" - isRequired={false} - isReadOnly={false} - value={profile_url} - onChange={(e) => { - let { value } = e.target; - if (onChange) { - const modelFields = { - TextAreaFieldbbd63464, - caption, - username, - profile_url: value, - post_url, - metadata, - nonModelField, - nonModelFieldArray, - }; - const result = onChange(modelFields); - value = result?.profile_url ?? value; + if (onSubmit) { + modelFields = onSubmit(modelFields); + } + try { + Object.entries(modelFields).forEach(([key, value]) => { + if (typeof value === \\"string\\" && value.trim() === \\"\\") { + modelFields[key] = undefined; + } + }); + const modelFieldsToSave = { + content: modelFields.content, + postID: modelFields.postID, + Post: modelFields.Post, + }; + await API.graphql({ + query: updateComment, + variables: { + input: { + id: commentRecord.id, + ...modelFieldsToSave, + }, + }, + }); + if (onSuccess) { + onSuccess(modelFields); } - if (errors.profile_url?.hasError) { - runValidationTasks(\\"profile_url\\", value); + } catch (err) { + if (onError) { + onError(modelFields, err.message); } - setProfile_url(value); - }} - onBlur={() => runValidationTasks(\\"profile_url\\", profile_url)} - errorMessage={errors.profile_url?.errorMessage} - hasError={errors.profile_url?.hasError} - {...getOverrideProps(overrides, \\"profile_url\\")} - ></TextField> + } + }} + {...getOverrideProps(overrides, \\"CommentUpdateForm\\")} + {...rest} + > <TextField - label=\\"Post url\\" + label=\\"Content\\" isRequired={false} isReadOnly={false} - value={post_url} + value={content} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { - TextAreaFieldbbd63464, - caption, - username, - profile_url, - post_url: value, - metadata, - nonModelField, - nonModelFieldArray, + content: value, + postID, + Post, + post: post1, }; const result = onChange(modelFields); - value = result?.post_url ?? value; + value = result?.content ?? value; } - if (errors.post_url?.hasError) { - runValidationTasks(\\"post_url\\", value); + if (errors.content?.hasError) { + runValidationTasks(\\"content\\", value); } - setPost_url(value); + setContent(value); }} - onBlur={() => runValidationTasks(\\"post_url\\", post_url)} - errorMessage={errors.post_url?.errorMessage} - hasError={errors.post_url?.hasError} - {...getOverrideProps(overrides, \\"post_url\\")} + onBlur={() => runValidationTasks(\\"content\\", content)} + errorMessage={errors.content?.errorMessage} + hasError={errors.content?.hasError} + {...getOverrideProps(overrides, \\"content\\")} ></TextField> - <TextAreaField - label=\\"Metadata\\" - isRequired={false} - isReadOnly={false} - value={metadata} - onChange={(e) => { - let { value } = e.target; - if (onChange) { - const modelFields = { - TextAreaFieldbbd63464, - caption, - username, - profile_url, - post_url, - metadata: value, - nonModelField, - nonModelFieldArray, - }; - const result = onChange(modelFields); - value = result?.metadata ?? value; - } - if (errors.metadata?.hasError) { - runValidationTasks(\\"metadata\\", value); - } - setMetadata(value); - }} - onBlur={() => runValidationTasks(\\"metadata\\", metadata)} - errorMessage={errors.metadata?.errorMessage} - hasError={errors.metadata?.hasError} - {...getOverrideProps(overrides, \\"metadata\\")} - ></TextAreaField> - <TextAreaField - label=\\"Non model field\\" - isRequired={false} - isReadOnly={false} - value={nonModelField} - onChange={(e) => { - let { value } = e.target; + <ArrayField + lengthLimit={1} + onChange={async (items) => { + let value = items[0]; if (onChange) { const modelFields = { - TextAreaFieldbbd63464, - caption, - username, - profile_url, - post_url, - metadata, - nonModelField: value, - nonModelFieldArray, + content, + postID: value, + Post, + post: post1, }; const result = onChange(modelFields); - value = result?.nonModelField ?? value; - } - if (errors.nonModelField?.hasError) { - runValidationTasks(\\"nonModelField\\", value); + value = result?.postID ?? value; } - setNonModelField(value); + setPostID(value); + setCurrentPostIDValue(undefined); }} - onBlur={() => runValidationTasks(\\"nonModelField\\", nonModelField)} - errorMessage={errors.nonModelField?.errorMessage} - hasError={errors.nonModelField?.hasError} - {...getOverrideProps(overrides, \\"nonModelField\\")} - ></TextAreaField> + currentFieldValue={currentPostIDValue} + label={\\"Post id\\"} + items={postID ? [postID] : []} + hasError={errors?.postID?.hasError} + errorMessage={errors?.postID?.errorMessage} + getBadgeText={(value) => + value + ? getDisplayValue.postID(postRecords.find((r) => r.id === value)) + : \\"\\" + } + setFieldValue={(value) => { + setCurrentPostIDDisplayValue( + value + ? getDisplayValue.postID(postRecords.find((r) => r.id === value)) + : \\"\\" + ); + setCurrentPostIDValue(value); + }} + inputFieldRef={postIDRef} + defaultFieldValue={\\"\\"} + > + <Autocomplete + label=\\"Post id\\" + isRequired={true} + isReadOnly={false} + placeholder=\\"Search Post\\" + value={currentPostIDDisplayValue} + options={postIDRecords + .filter( + (r, i, arr) => + arr.findIndex((member) => member?.id === r?.id) === i + ) + .map((r) => ({ + id: r?.id, + label: getDisplayValue.postID?.(r), + }))} + isLoading={postIDLoading} + onSelect={({ id, label }) => { + setCurrentPostIDValue(id); + setCurrentPostIDDisplayValue(label); + runValidationTasks(\\"postID\\", label); + }} + onClear={() => { + setCurrentPostIDDisplayValue(\\"\\"); + }} + defaultValue={postID} + onChange={(e) => { + let { value } = e.target; + fetchPostIDRecords(value); + if (errors.postID?.hasError) { + runValidationTasks(\\"postID\\", value); + } + setCurrentPostIDDisplayValue(value); + setCurrentPostIDValue(undefined); + }} + onBlur={() => runValidationTasks(\\"postID\\", currentPostIDValue)} + errorMessage={errors.postID?.errorMessage} + hasError={errors.postID?.hasError} + ref={postIDRef} + labelHidden={true} + {...getOverrideProps(overrides, \\"postID\\")} + ></Autocomplete> + </ArrayField> <ArrayField + lengthLimit={1} onChange={async (items) => { - let values = items; + let value = items[0]; if (onChange) { const modelFields = { - TextAreaFieldbbd63464, - caption, - username, - profile_url, - post_url, - metadata, - nonModelField, - nonModelFieldArray: values, + content, + postID, + Post: value, + post: post1, }; const result = onChange(modelFields); - values = result?.nonModelFieldArray ?? values; + value = result?.Post ?? value; } - setNonModelFieldArray(values); - setCurrentNonModelFieldArrayValue(\\"\\"); + setPost(value); + setCurrentPostValue(undefined); + setCurrentPostDisplayValue(\\"\\"); }} - currentFieldValue={currentNonModelFieldArrayValue} - label={\\"Non model field array\\"} - items={nonModelFieldArray} - hasError={errors?.nonModelFieldArray?.hasError} - errorMessage={errors?.nonModelFieldArray?.errorMessage} - setFieldValue={setCurrentNonModelFieldArrayValue} - inputFieldRef={nonModelFieldArrayRef} + currentFieldValue={currentPostValue} + label={\\"Post\\"} + items={Post ? [Post] : []} + hasError={errors?.Post?.hasError} + errorMessage={errors?.Post?.errorMessage} + getBadgeText={getDisplayValue.Post} + setFieldValue={(model) => { + setCurrentPostDisplayValue(model ? getDisplayValue.Post(model) : \\"\\"); + setCurrentPostValue(model); + }} + inputFieldRef={PostRef} defaultFieldValue={\\"\\"} > - <TextAreaField - label=\\"Non model field array\\" + <Autocomplete + label=\\"Post\\" isRequired={false} isReadOnly={false} - value={currentNonModelFieldArrayValue} + placeholder=\\"Search Post\\" + value={currentPostDisplayValue} + options={postRecords + .filter((r) => !PostIdSet.has(getIDValue.Post?.(r))) + .map((r) => ({ + id: getIDValue.Post?.(r), + label: getDisplayValue.Post?.(r), + }))} + isLoading={PostLoading} + onSelect={({ id, label }) => { + setCurrentPostValue( + postRecords.find((r) => + Object.entries(JSON.parse(id)).every( + ([key, value]) => r[key] === value + ) + ) + ); + setCurrentPostDisplayValue(label); + runValidationTasks(\\"Post\\", label); + }} + onClear={() => { + setCurrentPostDisplayValue(\\"\\"); + }} + defaultValue={Post} onChange={(e) => { let { value } = e.target; - if (errors.nonModelFieldArray?.hasError) { - runValidationTasks(\\"nonModelFieldArray\\", value); + fetchPostRecords(value); + if (errors.Post?.hasError) { + runValidationTasks(\\"Post\\", value); } - setCurrentNonModelFieldArrayValue(value); + setCurrentPostDisplayValue(value); + setCurrentPostValue(undefined); }} - onBlur={() => - runValidationTasks( - \\"nonModelFieldArray\\", - currentNonModelFieldArrayValue - ) - } - errorMessage={errors.nonModelFieldArray?.errorMessage} - hasError={errors.nonModelFieldArray?.hasError} - ref={nonModelFieldArrayRef} + onBlur={() => runValidationTasks(\\"Post\\", currentPostDisplayValue)} + errorMessage={errors.Post?.errorMessage} + hasError={errors.Post?.hasError} + ref={PostRef} labelHidden={true} - {...getOverrideProps(overrides, \\"nonModelFieldArray\\")} - ></TextAreaField> + {...getOverrideProps(overrides, \\"Post\\")} + ></Autocomplete> </ArrayField> + <TextField + label=\\"Label\\" + value={post1} + onChange={(e) => { + let { value } = e.target; + if (onChange) { + const modelFields = { + content, + postID, + Post, + post: value, + }; + const result = onChange(modelFields); + value = result?.post ?? value; + } + if (errors.post?.hasError) { + runValidationTasks(\\"post\\", value); + } + setPost1(value); + }} + onBlur={() => runValidationTasks(\\"post\\", post1)} + errorMessage={errors.post?.errorMessage} + hasError={errors.post?.hasError} + {...getOverrideProps(overrides, \\"post\\")} + ></TextField> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} @@ -5074,27 +6347,19 @@ export default function MyPostForm(props) { event.preventDefault(); resetStateValues(); }} - isDisabled={!(idProp || postModelProp)} + isDisabled={!(idProp || commentModelProp)} {...getOverrideProps(overrides, \\"ResetButton\\")} ></Button> <Flex gap=\\"15px\\" {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")} > - <Button - children=\\"Cancel\\" - type=\\"button\\" - onClick={() => { - onCancel && onCancel(); - }} - {...getOverrideProps(overrides, \\"CancelButton\\")} - ></Button> <Button children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" isDisabled={ - !(idProp || postModelProp) || + !(idProp || commentModelProp) || Object.values(errors).some((e) => e?.hasError) } {...getOverrideProps(overrides, \\"SubmitButton\\")} @@ -5107,65 +6372,52 @@ export default function MyPostForm(props) { " `; -exports[`amplify form renderer tests GraphQL form tests should generate a update form without relationships 2`] = ` +exports[`amplify form renderer tests GraphQL form tests should generate an update form with hasMany relationship 2`] = ` "import * as React from \\"react\\"; -import { GridProps, TextAreaFieldProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; +import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; -import { Post } from \\"../API\\"; +import { Comment, Post } from \\"../API\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; -export declare type MyPostFormInputValues = { - TextAreaFieldbbd63464?: string; - caption?: string; - username?: string; - profile_url?: string; - post_url?: string; - metadata?: string; - nonModelField?: string; - nonModelFieldArray?: string[]; +export declare type CommentUpdateFormInputValues = { + content?: string; + postID?: string; + Post?: Post; + post?: string; }; -export declare type MyPostFormValidationValues = { - TextAreaFieldbbd63464?: ValidationFunction<string>; - caption?: ValidationFunction<string>; - username?: ValidationFunction<string>; - profile_url?: ValidationFunction<string>; - post_url?: ValidationFunction<string>; - metadata?: ValidationFunction<string>; - nonModelField?: ValidationFunction<string>; - nonModelFieldArray?: ValidationFunction<string>; +export declare type CommentUpdateFormValidationValues = { + content?: ValidationFunction<string>; + postID?: ValidationFunction<string>; + Post?: ValidationFunction<Post>; + post?: ValidationFunction<string>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; -export declare type MyPostFormOverridesProps = { - MyPostFormGrid?: PrimitiveOverrideProps<GridProps>; - TextAreaFieldbbd63464?: PrimitiveOverrideProps<TextAreaFieldProps>; - caption?: PrimitiveOverrideProps<TextFieldProps>; - username?: PrimitiveOverrideProps<TextFieldProps>; - profile_url?: PrimitiveOverrideProps<TextFieldProps>; - post_url?: PrimitiveOverrideProps<TextFieldProps>; - metadata?: PrimitiveOverrideProps<TextAreaFieldProps>; - nonModelField?: PrimitiveOverrideProps<TextAreaFieldProps>; - nonModelFieldArray?: PrimitiveOverrideProps<TextAreaFieldProps>; +export declare type CommentUpdateFormOverridesProps = { + CommentUpdateFormGrid?: PrimitiveOverrideProps<GridProps>; + content?: PrimitiveOverrideProps<TextFieldProps>; + postID?: PrimitiveOverrideProps<AutocompleteProps>; + Post?: PrimitiveOverrideProps<AutocompleteProps>; + post?: PrimitiveOverrideProps<TextFieldProps>; } & EscapeHatchProps; -export declare type MyPostFormProps = React.PropsWithChildren<{ - overrides?: MyPostFormOverridesProps | undefined | null; +export declare type CommentUpdateFormProps = React.PropsWithChildren<{ + overrides?: CommentUpdateFormOverridesProps | undefined | null; } & { id?: string; - post?: Post; - onSubmit?: (fields: MyPostFormInputValues) => MyPostFormInputValues; - onSuccess?: (fields: MyPostFormInputValues) => void; - onError?: (fields: MyPostFormInputValues, errorMessage: string) => void; - onCancel?: () => void; - onChange?: (fields: MyPostFormInputValues) => MyPostFormInputValues; - onValidate?: MyPostFormValidationValues; + comment?: Comment; + onSubmit?: (fields: CommentUpdateFormInputValues) => CommentUpdateFormInputValues; + onSuccess?: (fields: CommentUpdateFormInputValues) => void; + onError?: (fields: CommentUpdateFormInputValues, errorMessage: string) => void; + onChange?: (fields: CommentUpdateFormInputValues) => CommentUpdateFormInputValues; + onValidate?: CommentUpdateFormValidationValues; } & React.CSSProperties>; -export default function MyPostForm(props: MyPostFormProps): React.ReactElement; +export default function CommentUpdateForm(props: CommentUpdateFormProps): React.ReactElement; " `; -exports[`amplify form renderer tests GraphQL form tests should generate an update form with hasMany relationship 1`] = ` +exports[`amplify form renderer tests GraphQL form tests should generate an update form with hasMany relationship without types file 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { @@ -5184,7 +6436,7 @@ import { import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { fetchByPath, validateField } from \\"./utils\\"; import { API } from \\"aws-amplify\\"; -import { getComment, listPosts } from \\"../graphql/queries\\"; +import { getComment, listPostIDS, listPosts } from \\"../graphql/queries\\"; import { updateComment } from \\"../graphql/mutations\\"; function ArrayField({ items = [], @@ -5469,7 +6721,7 @@ export default function CommentUpdateForm(props) { } const result = ( await API.graphql({ - query: listPosts, + query: listPostIDS, variables, }) ).data.listPostIDS.item; @@ -5831,11 +7083,10 @@ export default function CommentUpdateForm(props) { " `; -exports[`amplify form renderer tests GraphQL form tests should generate an update form with hasMany relationship 2`] = ` +exports[`amplify form renderer tests GraphQL form tests should generate an update form with hasMany relationship without types file 2`] = ` "import * as React from \\"react\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; -import { Comment, Post } from \\"../API\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; @@ -5844,13 +7095,13 @@ export declare type ValidationFunction<T> = (value: T, validationResponse: Valid export declare type CommentUpdateFormInputValues = { content?: string; postID?: string; - Post?: Post; + Post?: any; post?: string; }; export declare type CommentUpdateFormValidationValues = { content?: ValidationFunction<string>; postID?: ValidationFunction<string>; - Post?: ValidationFunction<Post>; + Post?: ValidationFunction<any>; post?: ValidationFunction<string>; }; export declare type PrimitiveOverrideProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; @@ -5865,7 +7116,7 @@ export declare type CommentUpdateFormProps = React.PropsWithChildren<{ overrides?: CommentUpdateFormOverridesProps | undefined | null; } & { id?: string; - comment?: Comment; + comment?: any; onSubmit?: (fields: CommentUpdateFormInputValues) => CommentUpdateFormInputValues; onSuccess?: (fields: CommentUpdateFormInputValues) => void; onError?: (fields: CommentUpdateFormInputValues, errorMessage: string) => void; diff --git a/packages/codegen-ui-react/lib/__tests__/studio-ui-codegen-react-forms.test.ts b/packages/codegen-ui-react/lib/__tests__/studio-ui-codegen-react-forms.test.ts index 4f381544..95019e99 100644 --- a/packages/codegen-ui-react/lib/__tests__/studio-ui-codegen-react-forms.test.ts +++ b/packages/codegen-ui-react/lib/__tests__/studio-ui-codegen-react-forms.test.ts @@ -15,6 +15,7 @@ */ /* eslint-disable no-template-curly-in-string */ import { ImportSource } from '../imports'; +import { ReactRenderConfig } from '../react-render-config'; import { defaultCLIRenderConfig, generateComponentOnlyWithAmplifyFormRenderer, @@ -708,6 +709,16 @@ describe('amplify form renderer tests', () => { }); describe('GraphQL form tests', () => { + const noTypesFileConfig: ReactRenderConfig = { + apiConfiguration: { + dataApi: 'GraphQL', + typesFilePath: '', + queriesFilePath: '../graphql/queries', + mutationsFilePath: '../graphql/mutations', + subscriptionsFilePath: '../graphql/subscriptions', + fragmentsFilePath: '../graphql/fragments', + }, + }; it('should generate a create form', () => { const { componentText, declaration } = generateWithAmplifyFormRenderer( 'forms/post-datastore-create', @@ -777,6 +788,24 @@ describe('amplify form renderer tests', () => { expect(declaration).toMatchSnapshot(); }); + it('should generate an update form with hasMany relationship without types file', () => { + const { componentText, declaration } = generateWithAmplifyFormRenderer( + 'forms/relationships/update-comment', + 'datastore/relationships/has-many-comment', + { ...defaultCLIRenderConfig, ...noTypesFileConfig }, + { isNonModelSupported: true, isRelationshipSupported: true }, + ); + + // check for import statement for graphql operation + expect(componentText).not.toContain('DataStore'); + + expect(componentText).toContain('await API.graphql({'); + expect(componentText).toContain('query: updateComment'); + + expect(componentText).toMatchSnapshot(); + expect(declaration).toMatchSnapshot(); + }); + it('should generate a relationship update form with autocomplete', () => { const { componentText, declaration } = generateWithAmplifyFormRenderer( 'forms/relationships/update-post', @@ -980,6 +1009,20 @@ describe('amplify form renderer tests', () => { expect(componentText).toMatchSnapshot(); expect(declaration).toMatchSnapshot(); }); + + it('should 1:1 relationships without types file path - Create', () => { + const { componentText, declaration } = generateWithAmplifyFormRenderer( + 'forms/owner-dog-create', + 'datastore/dog-owner-required', + { ...defaultCLIRenderConfig, ...noTypesFileConfig }, + { isNonModelSupported: true, isRelationshipSupported: true }, + ); + + expect(componentText).not.toContain('cannot be unlinked because'); + expect(componentText).not.toContain('cannot be linked to '); + expect(componentText).toMatchSnapshot(); + expect(declaration).toMatchSnapshot(); + }); }); it('should render form for child of bidirectional 1:m when field defined on parent', () => { diff --git a/packages/codegen-ui-react/lib/__tests__/studio-ui-codegen-react.test.ts b/packages/codegen-ui-react/lib/__tests__/studio-ui-codegen-react.test.ts index c3d3a0e2..e39a00ab 100644 --- a/packages/codegen-ui-react/lib/__tests__/studio-ui-codegen-react.test.ts +++ b/packages/codegen-ui-react/lib/__tests__/studio-ui-codegen-react.test.ts @@ -39,18 +39,6 @@ describe('amplify render tests', () => { expect(generatedCode.componentText).toMatchSnapshot(); }); - it('should generate a simple badge component', () => {}); - - it('should generate a simple card component', () => {}); - - it('should generate a simple divider component', () => {}); - - it('should generate a simple flex component', () => {}); - - it('should generate a simple image component', () => {}); - - it('should generate a simple string component', () => {}); - it('should generate a simple component without variant specific generation', () => { const generatedCode = generateWithAmplifyRenderer('buttonGolden'); expect(generatedCode.componentText.includes('restProp')).toBe(false); diff --git a/packages/codegen-ui-react/lib/forms/form-renderer-helper/type-helper.ts b/packages/codegen-ui-react/lib/forms/form-renderer-helper/type-helper.ts index 70d41002..1fdc03ef 100644 --- a/packages/codegen-ui-react/lib/forms/form-renderer-helper/type-helper.ts +++ b/packages/codegen-ui-react/lib/forms/form-renderer-helper/type-helper.ts @@ -40,6 +40,8 @@ import { DATA_TYPE_TO_TYPESCRIPT_MAP, FIELD_TYPE_TO_TYPESCRIPT_MAP } from './typ import { ImportCollection, ImportSource } from '../../imports'; import { PRIMITIVE_OVERRIDE_PROPS } from '../../primitive'; import { COMPOSITE_PRIMARY_KEY_PROP_NAME } from '../../utils/constants'; +import { ReactRenderConfig } from '../../react-render-config'; +import { isGraphqlConfig } from '../../utils/graphql'; type Node<T> = { [n: string]: T | Node<T>; @@ -51,13 +53,21 @@ type GetTypeNodeParam = { isArray: boolean; isValidation: boolean; importCollection?: ImportCollection; + renderConfig?: ReactRenderConfig; }; /** * based on the provided dataType (appsync scalar) * converts to the correct typescript type * default assumption is string type */ -const getTypeNode = ({ componentType, dataType, isArray, isValidation, importCollection }: GetTypeNodeParam) => { +const getTypeNode = ({ + componentType, + dataType, + isArray, + isValidation, + importCollection, + renderConfig, +}: GetTypeNodeParam) => { let typeNode: KeywordTypeNode | TypeReferenceNode = factory.createKeywordTypeNode(SyntaxKind.StringKeyword); if (componentType in FIELD_TYPE_TO_TYPESCRIPT_MAP) { @@ -71,7 +81,11 @@ const getTypeNode = ({ componentType, dataType, isArray, isValidation, importCol if (dataType && typeof dataType === 'object' && 'model' in dataType) { const modelName = dataType.model; const aliasedModel = importCollection?.addModelImport(modelName); - typeNode = factory.createTypeReferenceNode(factory.createIdentifier(aliasedModel || modelName)); + let identifier = aliasedModel || modelName; + if (isGraphqlConfig(renderConfig?.apiConfiguration) && !renderConfig?.apiConfiguration.typesFilePath) { + identifier = 'any'; + } + typeNode = factory.createTypeReferenceNode(factory.createIdentifier(identifier)); } if (isValidation) { @@ -157,13 +171,21 @@ export const generateFieldTypes = ( type: 'input' | 'validation', fieldConfigs: Record<string, FieldConfigMetadata>, importCollection?: ImportCollection, + renderConfig?: ReactRenderConfig, ) => { const nestedPaths: [fieldName: string, getTypeNodeParam: GetTypeNodeParam][] = []; const typeNodes: TypeElement[] = []; const isValidation = type === 'validation'; const typeName = isValidation ? getValidationTypeName(formName) : getInputValuesTypeName(formName); Object.entries(fieldConfigs).forEach(([fieldName, { dataType, componentType, isArray }]) => { - const getTypeNodeParam = { dataType, componentType, isArray: !!isArray, isValidation, importCollection }; + const getTypeNodeParam = { + dataType, + componentType, + isArray: !!isArray, + isValidation, + importCollection, + renderConfig, + }; const hasNestedFieldPath = fieldName.split('.').length > 1; if (hasNestedFieldPath) { nestedPaths.push([fieldName, getTypeNodeParam]); diff --git a/packages/codegen-ui-react/lib/forms/react-form-renderer.ts b/packages/codegen-ui-react/lib/forms/react-form-renderer.ts index 79f72ca2..9f945812 100644 --- a/packages/codegen-ui-react/lib/forms/react-form-renderer.ts +++ b/packages/codegen-ui-react/lib/forms/react-form-renderer.ts @@ -101,7 +101,7 @@ import { } from './form-renderer-helper/type-helper'; import { buildSelectedRecordsIdSet } from './form-renderer-helper/model-values'; import { COMPOSITE_PRIMARY_KEY_PROP_NAME } from '../utils/constants'; -import { getFetchRelatedRecordsCallbacks } from '../utils/graphql'; +import { getFetchRelatedRecordsCallbacks, isGraphqlConfig } from '../utils/graphql'; type RenderComponentOnlyResponse = { compText: string; @@ -343,12 +343,16 @@ export abstract class ReactFormTemplateRenderer extends StudioTemplateRenderer< modelName = this.importCollection.addModelImport(dataTypeName); } + if (isGraphqlConfig(this.renderConfig.apiConfiguration) && !this.renderConfig.apiConfiguration.typesFilePath) { + modelName = 'any'; + } + return [ validationResponseType, validationFunctionType, // pass in importCollection once to collect models to import - generateFieldTypes(formName, 'input', fieldConfigs, this.importCollection), - generateFieldTypes(formName, 'validation', fieldConfigs, this.importCollection), + generateFieldTypes(formName, 'input', fieldConfigs, this.importCollection, this.renderConfig), + generateFieldTypes(formName, 'validation', fieldConfigs, this.importCollection, this.renderConfig), primitiveOverrideProp, overrideTypeAliasDeclaration, factory.createTypeAliasDeclaration( diff --git a/packages/codegen-ui-react/lib/imports/import-collection.ts b/packages/codegen-ui-react/lib/imports/import-collection.ts index 72ff4417..b9bb9df1 100644 --- a/packages/codegen-ui-react/lib/imports/import-collection.ts +++ b/packages/codegen-ui-react/lib/imports/import-collection.ts @@ -200,46 +200,48 @@ export class ImportCollection { ], ) .concat( - Array.from(this.#collection).map(([moduleName, imports]) => { - const namedImports = [...imports].filter((namedImport) => namedImport !== 'default').sort(); - const aliasMap = this.importAlias.get(moduleName); - if (aliasMap) { - const importClause = factory.createImportClause( - false, - undefined, - factory.createNamedImports( - [...imports].map((item) => { - const alias = aliasMap.get(item); - return factory.createImportSpecifier( - alias && alias !== item ? factory.createIdentifier(item) : undefined, - factory.createIdentifier(alias ?? item), - ); - }), - ), - ); + Array.from(this.#collection) + .filter(([moduleName]) => moduleName) + .map(([moduleName, imports]) => { + const namedImports = [...imports].filter((namedImport) => namedImport !== 'default').sort(); + const aliasMap = this.importAlias.get(moduleName); + if (aliasMap) { + const importClause = factory.createImportClause( + false, + undefined, + factory.createNamedImports( + [...imports].map((item) => { + const alias = aliasMap.get(item); + return factory.createImportSpecifier( + alias && alias !== item ? factory.createIdentifier(item) : undefined, + factory.createIdentifier(alias ?? item), + ); + }), + ), + ); + return factory.createImportDeclaration( + undefined, + undefined, + importClause, + factory.createStringLiteral(moduleName), + ); + } return factory.createImportDeclaration( undefined, undefined, - importClause, + factory.createImportClause( + false, + // use module name as default import name + [...imports].indexOf('default') >= 0 ? factory.createIdentifier(path.basename(moduleName)) : undefined, + factory.createNamedImports( + namedImports.map((item) => { + return factory.createImportSpecifier(undefined, factory.createIdentifier(item)); + }), + ), + ), factory.createStringLiteral(moduleName), ); - } - return factory.createImportDeclaration( - undefined, - undefined, - factory.createImportClause( - false, - // use module name as default import name - [...imports].indexOf('default') >= 0 ? factory.createIdentifier(path.basename(moduleName)) : undefined, - factory.createNamedImports( - namedImports.map((item) => { - return factory.createImportSpecifier(undefined, factory.createIdentifier(item)); - }), - ), - ), - factory.createStringLiteral(moduleName), - ); - }), + }), ); return importDeclarations; diff --git a/packages/codegen-ui-react/lib/utils/graphql.ts b/packages/codegen-ui-react/lib/utils/graphql.ts index b673a6ea..00f560a1 100644 --- a/packages/codegen-ui-react/lib/utils/graphql.ts +++ b/packages/codegen-ui-react/lib/utils/graphql.ts @@ -29,6 +29,7 @@ import { ImportCollection, ImportValue } from '../imports'; import { capitalizeFirstLetter, getSetNameIdentifier, lowerCaseFirst } from '../helpers'; import { isBoundProperty, isConcatenatedProperty } from '../react-component-render-helper'; import { Primitive } from '../primitive'; +import { DataStoreRenderConfig, GraphqlRenderConfig } from '../react-render-config'; export enum ActionType { CREATE = 'create', @@ -39,6 +40,13 @@ export enum ActionType { GET_BY_RELATIONSHIP = 'getByRelationship', } +/* istanbul ignore next */ +export const isGraphqlConfig = ( + apiConfiguration?: GraphqlRenderConfig | DataStoreRenderConfig, +): apiConfiguration is GraphqlRenderConfig => { + return apiConfiguration?.dataApi === 'GraphQL'; +}; + export const getGraphqlQueryForModel = (action: ActionType, model: string, byFieldName = ''): string => { switch (action) { case ActionType.CREATE: