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 22c67b953..0ebde3209 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 @@ -799,6 +799,7 @@ export type CollectionOfCustomButtonsProps = React.PropsWithChildren< width?: Number; backgroundColor?: String; buttonColor?: UserPreferences; + buttonEnabled?: UserPreferences; items?: any[]; overrideItems?: (collectionItem: { item: any; @@ -815,6 +816,7 @@ export default function CollectionOfCustomButtons( width, backgroundColor, buttonColor: buttonColorProp, + buttonEnabled: buttonEnabledProp, items, overrideItems, overrides, @@ -824,6 +826,12 @@ export default function CollectionOfCustomButtons( and: [ { field: \\"age\\", operand: \\"10\\", operator: \\"gt\\" }, { field: \\"lastName\\", operand: \\"L\\", operator: \\"beginsWith\\" }, + { + and: [ + { field: \\"date\\", operator: \\"ge\\", operand: \\"2022-03-10\\" }, + { field: \\"date\\", operator: \\"le\\", operand: \\"2023-03-11\\" }, + ], + }, ], }; const buttonUserFilter = createDataStorePredicate(buttonUserFilterObj); @@ -854,6 +862,24 @@ export default function CollectionOfCustomButtons( }).items[0]; const buttonColor = buttonColorProp !== undefined ? buttonColorProp : buttonColorDataStore; + const buttonEnabledFilterObj = { + and: [ + { field: \\"date\\", operator: \\"ge\\", operand: \\"2022-03-10\\" }, + { field: \\"date\\", operator: \\"le\\", operand: \\"2023-03-11\\" }, + ], + }; + const buttonEnabledFilter = createDataStorePredicate( + buttonEnabledFilterObj + ); + const buttonEnabledDataStore = useDataStoreBinding({ + type: \\"collection\\", + model: UserPreferences, + criteria: buttonEnabledFilter, + }).items[0]; + const buttonEnabled = + buttonEnabledProp !== undefined + ? buttonEnabledProp + : buttonEnabledDataStore; return ( /* @ts-ignore: TS2322 */ { if (key === 'and' || key === 'or') { return factory.createPropertyAssignment( diff --git a/packages/codegen-ui/example-schemas/collectionWithBinding.json b/packages/codegen-ui/example-schemas/collectionWithBinding.json index 8f39ef98a..4e9d1e491 100644 --- a/packages/codegen-ui/example-schemas/collectionWithBinding.json +++ b/packages/codegen-ui/example-schemas/collectionWithBinding.json @@ -35,6 +35,17 @@ "operator": "eq" } } + }, + "buttonEnabled": { + "type": "Data", + "bindingProperties": { + "model": "UserPreferences", + "predicate": { + "field": "date", + "operand": "2022-03-102023-03-11", + "operator": "between" + } + } } }, "collectionProperties": { @@ -51,6 +62,11 @@ "field": "lastName", "operand": "L", "operator": "beginsWith" + }, + { + "field": "date", + "operand": "2022-03-102023-03-11", + "operator": "between" } ] } diff --git a/packages/codegen-ui/lib/__tests__/renderer-helper.test.ts b/packages/codegen-ui/lib/__tests__/renderer-helper.test.ts index b960fa98f..ca32a420f 100644 --- a/packages/codegen-ui/lib/__tests__/renderer-helper.test.ts +++ b/packages/codegen-ui/lib/__tests__/renderer-helper.test.ts @@ -27,6 +27,8 @@ import { isStudioComponentWithVariants, isEventPropertyBinding, isAuthProperty, + resolveBetweenPredicateToMultiplePredicates, + OPERAND_DELIMITER, } from '../renderer-helper'; describe('render-helper', () => { @@ -198,4 +200,44 @@ describe('render-helper', () => { Object.values(otherTypes).forEach((otherType) => expect(isAuthProperty(otherType)).toBeFalsy()); }); }); + + describe('resolveBetweenPredicateToMultiplePredicates', () => { + it('should throw if not 2 operands', () => { + expect(() => + resolveBetweenPredicateToMultiplePredicates({ + field: 'age', + operator: 'between', + operand: '1', + }), + ).toThrow(); + expect(() => + resolveBetweenPredicateToMultiplePredicates({ + field: 'age', + operator: 'between', + }), + ).toThrow(); + expect(() => + resolveBetweenPredicateToMultiplePredicates({ + field: 'age', + operator: 'between', + operand: [1, 2, 3].join(OPERAND_DELIMITER), + }), + ).toThrow(); + }); + + it('should resolve predicate', () => { + const betweenPredicateOperands = ['1', '2']; + const betweenPredicate = { + field: 'age', + operator: 'between', + operand: betweenPredicateOperands.join(OPERAND_DELIMITER), + }; + expect(resolveBetweenPredicateToMultiplePredicates(betweenPredicate)).toStrictEqual({ + and: [ + { field: betweenPredicate.field, operator: 'ge', operand: betweenPredicateOperands[0] }, + { field: betweenPredicate.field, operator: 'le', operand: betweenPredicateOperands[1] }, + ], + }); + }); + }); }); diff --git a/packages/codegen-ui/lib/renderer-helper.ts b/packages/codegen-ui/lib/renderer-helper.ts index ede57056f..34580c8e5 100644 --- a/packages/codegen-ui/lib/renderer-helper.ts +++ b/packages/codegen-ui/lib/renderer-helper.ts @@ -13,6 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +import { InvalidInputError } from './errors'; import { StudioComponent, StudioComponentAuthProperty, @@ -24,6 +25,7 @@ import { StudioComponentPropertyBinding, StudioComponentProperty, StudioComponentSlotBinding, + StudioComponentPredicate, } from './types'; import { breakpointSizes, BreakpointSizeType } from './utils/breakpoint-utils'; @@ -101,3 +103,28 @@ export function isEventPropertyBinding( export function isSlotBinding(prop: StudioComponentPropertyBinding): prop is StudioComponentSlotBinding { return typeof prop === 'object' && 'type' in prop && prop.type === 'Amplify.Slot'; } + +/** + For StudioComponentPredicate, there are cases when + we want multiple operands. This string indicates the end of + one operand and start of another. + Warning: if you change this, saved schemas may break. + */ +export const OPERAND_DELIMITER = ''; + +export function resolveBetweenPredicateToMultiplePredicates( + betweenPredicate: StudioComponentPredicate, +): StudioComponentPredicate { + const operands = betweenPredicate.operand?.split(OPERAND_DELIMITER); + + if (!operands || operands.length !== 2) { + throw new InvalidInputError('There must be 2 operands for a `between` predicate'); + } + + return { + and: [ + { field: betweenPredicate.field, operator: 'ge', operand: operands[0] }, + { field: betweenPredicate.field, operator: 'le', operand: operands[1] }, + ], + }; +} diff --git a/packages/test-generator/integration-test-templates/cypress/e2e/generate-spec.cy.ts b/packages/test-generator/integration-test-templates/cypress/e2e/generate-spec.cy.ts index 93f0c235b..c3d667d7e 100644 --- a/packages/test-generator/integration-test-templates/cypress/e2e/generate-spec.cy.ts +++ b/packages/test-generator/integration-test-templates/cypress/e2e/generate-spec.cy.ts @@ -70,6 +70,7 @@ const EXPECTED_SUCCESSFUL_CASES = new Set([ 'CollectionWithBindingItemsName', 'CompositeDogCard', 'CollectionWithCompositeKeysAndRelationships', + 'CollectionWithBetweenPredicate', 'PaginatedCollection', 'SearchableCollection', 'CustomParent', diff --git a/packages/test-generator/integration-test-templates/cypress/e2e/generated-components-spec.cy.ts b/packages/test-generator/integration-test-templates/cypress/e2e/generated-components-spec.cy.ts index 337f33809..75c392587 100644 --- a/packages/test-generator/integration-test-templates/cypress/e2e/generated-components-spec.cy.ts +++ b/packages/test-generator/integration-test-templates/cypress/e2e/generated-components-spec.cy.ts @@ -236,6 +236,15 @@ describe('Generated Components', () => { cy.contains('Toys: stick, ball'); }); }); + + it('Supports between predicates', () => { + cy.get('#collectionWithBetweenPredicate').within(() => { + cy.contains('Real'); + cy.contains('Last'); + cy.contains('Another').should('not.exist'); + cy.contains('Too Young').should('not.exist'); + }); + }); }); describe('Default Value', () => { diff --git a/packages/test-generator/integration-test-templates/src/ComponentTests.tsx b/packages/test-generator/integration-test-templates/src/ComponentTests.tsx index 3c61a4c10..d4c8b9503 100644 --- a/packages/test-generator/integration-test-templates/src/ComponentTests.tsx +++ b/packages/test-generator/integration-test-templates/src/ComponentTests.tsx @@ -61,6 +61,7 @@ import { ComponentWithAuthBinding, DataBindingNamedClass, CollectionWithCompositeKeysAndRelationships, + CollectionWithBetweenPredicate, } from './ui-components'; // eslint-disable-line import/extensions import { initializeAuthMockData } from './mock-utils'; @@ -420,6 +421,7 @@ export default function ComponentTests() { }; }} /> +

Default Value

diff --git a/packages/test-generator/lib/components/collections/collectionWithBetweenPredicate.json b/packages/test-generator/lib/components/collections/collectionWithBetweenPredicate.json new file mode 100644 index 000000000..62de246f9 --- /dev/null +++ b/packages/test-generator/lib/components/collections/collectionWithBetweenPredicate.json @@ -0,0 +1,59 @@ +{ + "id": "1234-5678-9010", + "componentType": "Collection", + "name": "CollectionWithBetweenPredicate", + "properties": { + "type": { + "value": "list" + }, + "isPaginated": { + "value": true + }, + "gap": { + "value": "1.5rem" + } + }, + "bindingProperties": {}, + "collectionProperties": { + "buttonUser": { + "model": "User", + "predicate": { + "and": [ + { + "field": "age", + "operand": "571", + "operator": "between" + }, + { + "field": "lastName", + "operand": "LUser0", + "operator": "ne" + } + ] + } + } + }, + "children": [ + { + "componentType": "Flex", + "name": "MyFlex", + "properties": {}, + "children": [ + { + "componentType": "Button", + "name": "MyButton", + "properties": { + "children": { + "collectionBindingProperties": { + "property": "buttonUser", + "field": "firstName" + }, + "defaultValue": "hspain@gmail.com" + } + } + } + ] + } + ], + "schemaVersion": "1.0" +} diff --git a/packages/test-generator/lib/components/collections/index.ts b/packages/test-generator/lib/components/collections/index.ts index f0aa38dcf..70d52f671 100644 --- a/packages/test-generator/lib/components/collections/index.ts +++ b/packages/test-generator/lib/components/collections/index.ts @@ -21,3 +21,4 @@ export { default as PaginatedCollection } from './paginatedCollection.json'; export { default as SearchableCollection } from './searchableCollection.json'; export { default as SimpleUserCollection } from './simpleUserCollection.json'; export { default as CollectionWithCompositeKeysAndRelationships } from './collectionWithCompositeKeysAndRelationships.json'; +export { default as CollectionWithBetweenPredicate } from './collectionWithBetweenPredicate.json';