diff --git a/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react.test.ts.snap b/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react.test.ts.snap index 65e32a5a5..bb4995f56 100644 --- a/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react.test.ts.snap +++ b/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react.test.ts.snap @@ -5159,6 +5159,56 @@ export default function SocialA(props: SocialAProps): React.ReactElement { " `; +exports[`amplify render tests mutations controls an input that is modified by a button 1`] = ` +Object { + "componentText": "/* eslint-disable */ +import React from \\"react\\"; +import { + EscapeHatchProps, + getOverrideProps, +} from \\"@aws-amplify/ui-react/internal\\"; +import { Button, Flex, FlexProps, TextField } from \\"@aws-amplify/ui-react\\"; +import { useStateMutationAction } from \\"../mock-helpers\\"; + +export type InputMutationOnClickProps = React.PropsWithChildren< + Partial & { + overrides?: EscapeHatchProps | undefined | null; + } +>; +export default function InputMutationOnClick( + props: InputMutationOnClickProps +): React.ReactElement { + const { overrides, ...rest } = props; + const [myInputValue, setMyInputValue] = useStateMutationAction(undefined); + const setInputButtonClick = () => { + setMyInputValue(\\"Razor Crest\\"); + }; + return ( + /* @ts-ignore: TS2322 */ + + { + setMyInputValue(event.target.value); + }} + {...getOverrideProps(overrides, \\"MyInput\\")} + > + + + ); +} +", + "declaration": undefined, + "renderComponentToFilesystem": [Function], +} +`; + exports[`amplify render tests mutations form 1`] = ` Object { "componentText": "/* eslint-disable */ 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 ffc863be3..a3f52f4db 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 @@ -361,6 +361,10 @@ describe('amplify render tests', () => { it('modifies text in component on input change', () => { expect(generateWithAmplifyRenderer('workflow/inputToTextChange')).toMatchSnapshot(); }); + + it('controls an input that is modified by a button', () => { + expect(generateWithAmplifyRenderer('workflow/inputMutationOnClick')).toMatchSnapshot(); + }); }); describe('default value', () => { diff --git a/packages/codegen-ui-react/lib/primitive.ts b/packages/codegen-ui-react/lib/primitive.ts index b0e407a6a..e0eecd91e 100644 --- a/packages/codegen-ui-react/lib/primitive.ts +++ b/packages/codegen-ui-react/lib/primitive.ts @@ -108,3 +108,19 @@ export const PrimitiveTypeParameter: Partial< export function isBuiltInIcon(componentType: string): boolean { return iconset.has(componentType); } + +/* + * temporary list of Primitives with a change event. Final implementation will pull from amplify UI + */ +export const PrimitivesWithChangeEvent: Set = new Set([ + Primitive.CheckboxField, + Primitive.PasswordField, + Primitive.PhoneNumberField, + Primitive.RadioGroupField, + Primitive.SearchField, + Primitive.SelectField, + Primitive.SliderField, + Primitive.StepperField, + Primitive.SwitchField, + Primitive.TextField, +]); diff --git a/packages/codegen-ui-react/lib/workflow/mutation.ts b/packages/codegen-ui-react/lib/workflow/mutation.ts index 28f582fff..e423597aa 100644 --- a/packages/codegen-ui-react/lib/workflow/mutation.ts +++ b/packages/codegen-ui-react/lib/workflow/mutation.ts @@ -32,6 +32,7 @@ import { getSetStateName, } from '../react-component-render-helper'; import { ImportCollection, ImportValue } from '../imports'; +import Primitive, { PrimitivesWithChangeEvent } from '../primitive'; import { mapGenericEventToReact } from './events'; import { getChildPropMappingForComponentName } from './utils'; @@ -246,17 +247,21 @@ export function filterStateReferencesForComponent( ): MutationReferences { return stateReferences .filter(({ componentName }) => componentName === component.name) - .reduce(mutationReferenceReducer, {}); + .reduce(mutationReferenceReducerWithComponentType(component.componentType), {}); } -function mutationReferenceReducer( - mutationReferences: MutationReferences, - stateReference: StateReference, -): MutationReferences { - const propertyReferences = - stateReference.property in mutationReferences ? mutationReferences[stateReference.property] : []; - return { - ...mutationReferences, - [stateReference.property]: propertyReferences.concat([{ addControlEvent: !('set' in stateReference) }]), +function mutationReferenceReducerWithComponentType(componentType: string) { + return function mutationReferenceReducer( + mutationReferences: MutationReferences, + stateReference: StateReference, + ): MutationReferences { + const propertyReferences = + stateReference.property in mutationReferences ? mutationReferences[stateReference.property] : []; + return { + ...mutationReferences, + [stateReference.property]: propertyReferences.concat([ + { addControlEvent: PrimitivesWithChangeEvent.has(componentType as Primitive) || !('set' in stateReference) }, + ]), + }; }; } diff --git a/packages/codegen-ui/example-schemas/workflow/inputMutationOnClick.json b/packages/codegen-ui/example-schemas/workflow/inputMutationOnClick.json new file mode 100644 index 000000000..14db30dbe --- /dev/null +++ b/packages/codegen-ui/example-schemas/workflow/inputMutationOnClick.json @@ -0,0 +1,36 @@ +{ + "id": "1234-5678-9010", + "componentType": "Flex", + "name": "InputMutationOnClick", + "properties": {}, + "children": [ + { + "componentType": "TextField", + "name": "MyInput", + "properties": {} + }, + { + "componentType": "Button", + "name": "SetInputButton", + "events": { + "click": { + "action": "Amplify.Mutation", + "parameters": { + "state": { + "componentName": "MyInput", + "property": "value", + "set": { + "value": "Razor Crest" + } + } + } + } + }, + "properties": { + "children": { + "value": "Change Input" + } + } + } + ] +} diff --git a/packages/test-generator/integration-test-templates/cypress/integration/generate-spec.ts b/packages/test-generator/integration-test-templates/cypress/integration/generate-spec.ts index 9405980fd..194dc52a2 100644 --- a/packages/test-generator/integration-test-templates/cypress/integration/generate-spec.ts +++ b/packages/test-generator/integration-test-templates/cypress/integration/generate-spec.ts @@ -143,6 +143,7 @@ const EXPECTED_SUCCESSFUL_CASES = new Set([ 'FormWithState', 'InternalMutation', 'TwoWayBindings', + 'InputMutationOnClick', ]); const EXPECTED_INVALID_INPUT_CASES = new Set([ 'ComponentMissingProperties', diff --git a/packages/test-generator/integration-test-templates/cypress/integration/workflow-spec.ts b/packages/test-generator/integration-test-templates/cypress/integration/workflow-spec.ts index d34749370..2946baf8e 100644 --- a/packages/test-generator/integration-test-templates/cypress/integration/workflow-spec.ts +++ b/packages/test-generator/integration-test-templates/cypress/integration/workflow-spec.ts @@ -223,6 +223,18 @@ describe('Workflow', () => { cy.get('#NoInitialDisplayBlockButton').click(); cy.get('#NoInitialTextDisplay').should('be.visible'); }); + + it('supports mutations on controlled components', () => { + cy.get('#input-mutation-on-click').within(() => { + cy.get('input').should('have.attr', 'value', ''); + cy.get('button').click(); + cy.get('input').should('have.attr', 'value', 'Razor Crest'); + cy.get('input').type(' blew up'); + cy.get('input').should('have.attr', 'value', 'Razor Crest blew up'); + cy.get('button').click(); + cy.get('input').should('have.attr', 'value', 'Razor Crest'); + }); + }); }); }); }); diff --git a/packages/test-generator/integration-test-templates/src/WorkflowTests.tsx b/packages/test-generator/integration-test-templates/src/WorkflowTests.tsx index 20755509b..4a82d67f0 100644 --- a/packages/test-generator/integration-test-templates/src/WorkflowTests.tsx +++ b/packages/test-generator/integration-test-templates/src/WorkflowTests.tsx @@ -30,6 +30,7 @@ import { DataStoreActions, FormWithState, SimpleUserCollection, + InputMutationOnClick, } from './ui-components'; // eslint-disable-line import/extensions type AuthState = 'LoggedIn' | 'LoggedOutLocally' | 'LoggedOutGlobally' | 'Error'; @@ -216,6 +217,7 @@ export default function ComplexTests() { + ); diff --git a/packages/test-generator/lib/components/workflow/index.ts b/packages/test-generator/lib/components/workflow/index.ts index e5946cd7f..0c599fa26 100644 --- a/packages/test-generator/lib/components/workflow/index.ts +++ b/packages/test-generator/lib/components/workflow/index.ts @@ -24,3 +24,4 @@ export { default as UpdateVisibility } from './updateVisibility.json'; export { default as DataStoreActions } from './dataStoreActions.json'; export { default as FormWithState } from './formWithState.json'; export { default as TwoWayBindings } from './twoWayBindings.json'; +export { default as InputMutationOnClick } from './inputMutationOnClick.json'; diff --git a/packages/test-generator/lib/components/workflow/inputMutationOnClick.json b/packages/test-generator/lib/components/workflow/inputMutationOnClick.json new file mode 100644 index 000000000..7f6b3be23 --- /dev/null +++ b/packages/test-generator/lib/components/workflow/inputMutationOnClick.json @@ -0,0 +1,40 @@ +{ + "id": "1234-5678-9010", + "componentType": "Flex", + "name": "InputMutationOnClick", + "properties": { + "id": { + "value": "input-mutation-on-click" + } + }, + "children": [ + { + "componentType": "TextField", + "name": "MyInput", + "properties": {} + }, + { + "componentType": "Button", + "name": "SetInputButton", + "events": { + "click": { + "action": "Amplify.Mutation", + "parameters": { + "state": { + "componentName": "MyInput", + "property": "value", + "set": { + "value": "Razor Crest" + } + } + } + } + }, + "properties": { + "children": { + "value": "Change Input" + } + } + } + ] +}