diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index dabb1fe1c..81fff05c1 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -20,10 +20,10 @@ jobs: with: path: amplify-cli repository: aws-amplify/amplify-cli - - name: Setup Node.js LTS + - name: Setup Node.js LTS/gallium uses: actions/setup-node@v2 with: - node-version: lts/* + node-version: lts/gallium - name: Build amplify-codegen-ui working-directory: amplify-codegen-ui run: | 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 8b71daa80..58dbc9dc1 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 @@ -345,6 +345,7 @@ exports[`amplify render tests actions with conditional in parameters 1`] = ` Object { "componentText": "/* eslint-disable */ import * as React from \\"react\\"; +import { User } from \\"../models\\"; import { EscapeHatchProps, createDataStorePredicate, @@ -352,7 +353,6 @@ import { useDataStoreBinding, useStateMutationAction, } from \\"@aws-amplify/ui-react/internal\\"; -import { User } from \\"../models\\"; import { Button, Flex, FlexProps, Text } from \\"@aws-amplify/ui-react\\"; export type ConditionalInMutationProps = React.PropsWithChildren< @@ -578,6 +578,39 @@ export default function DataBindingNamedClass( " `; +exports[`amplify render tests bindings data supports model with conflicting component type 1`] = ` +"/* eslint-disable */ +import * as React from \\"react\\"; +import { Flex as Flex0 } from \\"../models\\"; +import { + EscapeHatchProps, + getOverrideProps, +} from \\"@aws-amplify/ui-react/internal\\"; +import { Text, TextProps } from \\"@aws-amplify/ui-react\\"; + +export type DataBindingNamedClassProps = React.PropsWithChildren< + Partial & { + class?: Flex0; + } & { + overrides?: EscapeHatchProps | undefined | null; + } +>; +export default function DataBindingNamedClass( + props: DataBindingNamedClassProps +): React.ReactElement { + const { class: classProp, overrides, ...rest } = props; + return ( + /* @ts-ignore: TS2322 */ + + ); +} +" +`; + exports[`amplify render tests collection should not render nested query if the data schema is not provided 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; @@ -639,13 +672,13 @@ export default function AuthorProfileCollection( exports[`amplify render tests collection should render collection with data binding 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; +import { UserPreferences, User } from \\"../models\\"; import { EscapeHatchProps, createDataStorePredicate, getOverrideProps, useDataStoreBinding, } from \\"@aws-amplify/ui-react/internal\\"; -import { User, UserPreferences } from \\"../models\\"; import { Button, Collection, @@ -742,6 +775,7 @@ exports[`amplify render tests collection should render collection with data bind Object { "componentText": "/* eslint-disable */ import * as React from \\"react\\"; +import { UserPreferences, User } from \\"../models\\"; import { EscapeHatchProps, createDataStorePredicate, @@ -749,7 +783,6 @@ import { useDataStoreBinding, } from \\"@aws-amplify/ui-react/internal\\"; import { SortDirection, SortPredicate } from \\"@aws-amplify/datastore\\"; -import { User, UserPreferences } from \\"../models\\"; import { Button, Collection, @@ -853,13 +886,13 @@ export default function CollectionOfCustomButtons( exports[`amplify render tests collection should render collection with data binding if binding name is items 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; +import { UserPreferences, User } from \\"../models\\"; import { EscapeHatchProps, createDataStorePredicate, getOverrideProps, useDataStoreBinding, } from \\"@aws-amplify/ui-react/internal\\"; -import { User, UserPreferences } from \\"../models\\"; import { Button, Collection, @@ -1058,6 +1091,123 @@ export default function ListingCardCollection( " `; +exports[`amplify render tests collection should render if model name collides with component types 1`] = ` +"/* eslint-disable */ +import * as React from \\"react\\"; +import { Flex as Flex0, FlexModel, Button as Button0 } from \\"../models\\"; +import { + EscapeHatchProps, + createDataStorePredicate, + getOverrideProps, + useDataStoreBinding, +} from \\"@aws-amplify/ui-react/internal\\"; +import { SortDirection, SortPredicate } from \\"@aws-amplify/datastore\\"; +import { + Button, + Collection, + CollectionProps, + Flex, +} from \\"@aws-amplify/ui-react\\"; +import { MyFlexProps } from \\"./MyFlex\\"; + +export type CollectionWithModelNameCollisionsProps = React.PropsWithChildren< + Partial> & { + backgroundColor?: String; + buttonColor?: Flex0; + buttonShape?: FlexModel; + items?: any[]; + overrideItems?: (collectionItem: { + item: any; + index: number; + }) => MyFlexProps; + } & { + overrides?: EscapeHatchProps | undefined | null; + } +>; +export default function CollectionWithModelNameCollisions( + props: CollectionWithModelNameCollisionsProps +): React.ReactElement { + const { + backgroundColor, + buttonColor: buttonColorProp, + buttonShape: buttonShapeProp, + items, + overrideItems, + overrides, + ...rest + } = props; + const buttonModelFilterObj = { + and: [ + { field: \\"age\\", operand: \\"10\\", operator: \\"gt\\" }, + { field: \\"lastName\\", operand: \\"L\\", operator: \\"beginsWith\\" }, + ], + }; + const buttonModelFilter = + createDataStorePredicate(buttonModelFilterObj); + const buttonModelPagination = { + sort: (s: SortPredicate) => s.lastName(SortDirection.ASCENDING), + }; + const buttonModelDataStore = useDataStoreBinding({ + type: \\"collection\\", + model: Button0, + criteria: buttonModelFilter, + pagination: buttonModelPagination, + }).items; + const buttonModel = items !== undefined ? items : buttonModelDataStore; + const buttonColorFilterObj = { + field: \\"userID\\", + operand: \\"user@email.com\\", + operator: \\"eq\\", + }; + const buttonColorFilter = + createDataStorePredicate(buttonColorFilterObj); + const buttonColorDataStore = useDataStoreBinding({ + type: \\"collection\\", + model: Flex0, + criteria: buttonColorFilter, + }).items[0]; + const buttonColor = + buttonColorProp !== undefined ? buttonColorProp : buttonColorDataStore; + const buttonShapeFilterObj = { + field: \\"userID\\", + operand: \\"user@email.com\\", + operator: \\"eq\\", + }; + const buttonShapeFilter = + createDataStorePredicate(buttonShapeFilterObj); + const buttonShapeDataStore = useDataStoreBinding({ + type: \\"collection\\", + model: FlexModel, + criteria: buttonShapeFilter, + }).items[0]; + const buttonShape = + buttonShapeProp !== undefined ? buttonShapeProp : buttonShapeDataStore; + return ( + /* @ts-ignore: TS2322 */ + + {(item, index) => ( + + + + )} + + ); +} +" +`; + exports[`amplify render tests collection should render nested query if model has a hasMany relationship 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; @@ -5981,6 +6131,7 @@ exports[`amplify render tests mutations supports all initial value binding types Object { "componentText": "/* eslint-disable */ import * as React from \\"react\\"; +import { User } from \\"../models\\"; import { EscapeHatchProps, createDataStorePredicate, @@ -5989,7 +6140,6 @@ import { useDataStoreBinding, useStateMutationAction, } from \\"@aws-amplify/ui-react/internal\\"; -import { User } from \\"../models\\"; import { useEffect } from \\"react\\"; import { Button, @@ -6283,12 +6433,12 @@ exports[`amplify render tests mutations supports invalid statement names for mut Object { "componentText": "/* eslint-disable */ import * as React from \\"react\\"; +import { Listing } from \\"../models\\"; import { EscapeHatchProps, getOverrideProps, useStateMutationAction, } from \\"@aws-amplify/ui-react/internal\\"; -import { Listing } from \\"../models\\"; import { Flex, FlexProps, Image, Text } from \\"@aws-amplify/ui-react\\"; export type CardAProps = React.PropsWithChildren< diff --git a/packages/codegen-ui-react/lib/__tests__/imports/import-collection.test.ts b/packages/codegen-ui-react/lib/__tests__/imports/import-collection.test.ts index c57816c7a..233e80713 100644 --- a/packages/codegen-ui-react/lib/__tests__/imports/import-collection.test.ts +++ b/packages/codegen-ui-react/lib/__tests__/imports/import-collection.test.ts @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { ImportCollection } from '../../imports'; +import { ImportCollection, ImportSource } from '../../imports'; import { assertASTMatchesSnapshot } from '../__utils__'; function assertImportCollectionMatchesSnapshot(importCollection: ImportCollection) { @@ -69,6 +69,22 @@ describe('ImportCollection', () => { importCollection.addImport('@aws-amplify/ui-react', 'getOverrideProps'); assertImportCollectionMatchesSnapshot(importCollection); }); + test('model imports colliding with exisiting imports', () => { + const importCollection = new ImportCollection({ + componentNameToTypeMap: { TextInput: 'TextField' }, + hasAuthBindings: false, + requiredDataModels: [], + stateReferences: [], + }); + importCollection.addImport(ImportSource.LOCAL_MODELS, 'TextField0'); + importCollection.addImport(ImportSource.LOCAL_MODELS, 'TextField'); + importCollection.addImport(ImportSource.LOCAL_MODELS, 'TestModel'); + importCollection.addImport(ImportSource.LOCAL_MODELS, 'ButtonProps'); + expect(importCollection.getMappedAlias(ImportSource.LOCAL_MODELS, 'TextField')).toEqual('TextField1'); + expect(importCollection.getMappedAlias(ImportSource.LOCAL_MODELS, 'TextField0')).toEqual('TextField0'); + expect(importCollection.getMappedAlias(ImportSource.LOCAL_MODELS, 'TestModel')).toEqual('TestModel'); + expect(importCollection.getMappedAlias(ImportSource.LOCAL_MODELS, 'ButtonProps')).toEqual('ButtonProps0'); + }); }); test('mergeCollections', () => { 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 ca62e247d..6670d8790 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 @@ -134,6 +134,10 @@ describe('amplify render tests', () => { const { componentText } = generateWithAmplifyRenderer('authorCollectionComponent'); expect(componentText).toMatchSnapshot(); }); + it('should render if model name collides with component types', () => { + const { componentText } = generateWithAmplifyRenderer('collectionWithModelNameCollisions'); + expect(componentText).toMatchSnapshot(); + }); }); describe('complex examples', () => { @@ -594,6 +598,9 @@ describe('amplify render tests', () => { it('supports bindings with reserved keywords', () => { expect(generateWithAmplifyRenderer('bindings/data/dataBindingNamedClass').componentText).toMatchSnapshot(); }); + it('supports model with conflicting component type', () => { + expect(generateWithAmplifyRenderer('bindings/data/dataBindingNamedFlex').componentText).toMatchSnapshot(); + }); }); }); }); diff --git a/packages/codegen-ui-react/lib/helpers/index.ts b/packages/codegen-ui-react/lib/helpers/index.ts index 6d9e1095d..d101c901f 100644 --- a/packages/codegen-ui-react/lib/helpers/index.ts +++ b/packages/codegen-ui-react/lib/helpers/index.ts @@ -14,3 +14,15 @@ limitations under the License. */ export const lowerCaseFirst = (input: string) => input.charAt(0).toLowerCase() + input.slice(1); + +export const createUniqueName = (name: string, isNameUsed: (input: string) => boolean) => { + if (!isNameUsed(name)) { + return name; + } + let count = 0; + const prospectiveNewName = name; + while (isNameUsed(prospectiveNewName + count)) { + count += 1; + } + return prospectiveNewName + count; +}; diff --git a/packages/codegen-ui-react/lib/imports/import-collection.ts b/packages/codegen-ui-react/lib/imports/import-collection.ts index 32f4716d0..9b44ab703 100644 --- a/packages/codegen-ui-react/lib/imports/import-collection.ts +++ b/packages/codegen-ui-react/lib/imports/import-collection.ts @@ -15,11 +15,22 @@ */ import { ImportDeclaration, factory } from 'typescript'; import path from 'path'; +import { ComponentMetadata } from '@aws-amplify/codegen-ui/lib/utils'; import { ImportMapping, ImportValue, ImportSource } from './import-mapping'; +import { isPrimitive } from '../primitive'; +import { createUniqueName } from '../helpers'; export class ImportCollection { + constructor(componentMetadata?: ComponentMetadata) { + this.importedNames = new Set(Object.values(componentMetadata?.componentNameToTypeMap || {})); + } + + importedNames: Set; + #collection: Map> = new Map(); + importAlias: Map> = new Map(); + addMappedImport(importValue: ImportValue) { const importPackage = ImportMapping[importValue]; this.addImport(importPackage, importValue); @@ -34,13 +45,41 @@ export class ImportCollection { if (!existingPackage?.has(importName)) { existingPackage?.add(importName); + if (packageName !== ImportSource.LOCAL_MODELS) { + this.importedNames.add(importName); + } + } + + if (packageName === ImportSource.LOCAL_MODELS) { + const existingPackageAlias = this.importAlias.get(packageName); + const existingAlias = existingPackageAlias?.get(importName); + if (existingAlias) return existingAlias; + const modelAlias = createUniqueName( + importName, + (input) => + this.importedNames.has(input) || (input.endsWith('Props') && isPrimitive(input.replace(/Props/g, ''))), + ); + if (existingPackageAlias) { + existingPackageAlias.set(importName, modelAlias); + } else { + const aliasMap = new Map(); + aliasMap.set(importName, modelAlias); + this.importAlias.set(packageName, aliasMap); + } + this.importedNames.add(modelAlias); + return modelAlias; } + return importName; } removeImportSource(packageImport: ImportSource) { this.#collection.delete(packageImport); } + getMappedAlias(packageName: string, importName: string) { + return this.importAlias.get(packageName)?.get(importName) || importName; + } + mergeCollections(otherCollection: ImportCollection) { otherCollection.#collection.forEach((value, key) => { [...value].forEach((singlePackage) => { @@ -87,6 +126,28 @@ 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), + ); + }), + ), + ); + return factory.createImportDeclaration( + undefined, + undefined, + importClause, + factory.createStringLiteral(moduleName), + ); + } return factory.createImportDeclaration( undefined, undefined, diff --git a/packages/codegen-ui-react/lib/react-studio-template-renderer.ts b/packages/codegen-ui-react/lib/react-studio-template-renderer.ts index f8c8c95af..ca910d696 100644 --- a/packages/codegen-ui-react/lib/react-studio-template-renderer.ts +++ b/packages/codegen-ui-react/lib/react-studio-template-renderer.ts @@ -64,7 +64,7 @@ import { ImportCollection, ImportSource, ImportValue } from './imports'; import { ReactOutputManager } from './react-output-manager'; import { ReactRenderConfig, ScriptKind, scriptKindToFileExtension } from './react-render-config'; import SampleCodeRenderer from './amplify-ui-renderers/sampleCodeRenderer'; -import { getComponentPropName } from './react-component-render-helper'; +import { addBindingPropertiesImports, getComponentPropName } from './react-component-render-helper'; import { transpile, buildPrinter, @@ -100,7 +100,7 @@ export abstract class ReactStudioTemplateRenderer extends StudioTemplateRenderer renderComponentToFilesystem: (outputPath: string) => Promise; } > { - protected importCollection = new ImportCollection(); + protected importCollection: ImportCollection; protected renderConfig: RequiredKeys; @@ -123,6 +123,8 @@ export abstract class ReactStudioTemplateRenderer extends StudioTemplateRenderer this.mapSyntheticPropsForVariants(); this.mapSyntheticProps(); this.dataSchema = dataSchema; + this.importCollection = new ImportCollection(this.componentMetadata); + addBindingPropertiesImports(this.component, this.importCollection); // TODO: throw warnings on invalid config combinations. i.e. CommonJS + JSX } @@ -440,11 +442,15 @@ export abstract class ReactStudioTemplateRenderer extends StudioTemplateRenderer ); propSignatures.push(propSignature); } else if (isDataPropertyBinding(binding)) { + const modelName = this.importCollection.getMappedAlias( + ImportSource.LOCAL_MODELS, + binding.bindingProperties.model, + ); const propSignature = factory.createPropertySignature( undefined, propName, factory.createToken(SyntaxKind.QuestionToken), - factory.createTypeReferenceNode(binding.bindingProperties.model, undefined), + factory.createTypeReferenceNode(modelName, undefined), ); propSignatures.push(propSignature); } else if (isEventPropertyBinding(binding)) { @@ -861,20 +867,20 @@ export abstract class ReactStudioTemplateRenderer extends StudioTemplateRenderer private buildCollectionBindingStatements(component: StudioComponent): Statement[] { const statements: Statement[] = []; - if (isStudioComponentWithCollectionProperties(component)) { Object.entries(component.collectionProperties).forEach((collectionProp) => { const [propName, { model, sort, predicate }] = collectionProp; + const modelName = this.importCollection.addImport(ImportSource.LOCAL_MODELS, model); + if (predicate) { statements.push(this.buildPredicateDeclaration(propName, predicate)); - statements.push(this.buildCreateDataStorePredicateCall(model, propName)); + statements.push(this.buildCreateDataStorePredicateCall(modelName, propName)); } if (sort) { this.importCollection.addMappedImport(ImportValue.SORT_DIRECTION); this.importCollection.addMappedImport(ImportValue.SORT_PREDICATE); - statements.push(this.buildPaginationStatement(propName, model, sort)); + statements.push(this.buildPaginationStatement(propName, modelName, sort)); } - this.importCollection.addImport(ImportSource.LOCAL_MODELS, model); /** * const userDataStore = useDataStoreBinding({ * type: "collection", @@ -884,7 +890,7 @@ export abstract class ReactStudioTemplateRenderer extends StudioTemplateRenderer */ statements.push( ...this.buildCollectionBindingCall( - model, + modelName, this.getDataStoreName(propName), predicate ? this.getFilterName(propName) : undefined, sort ? this.getPaginationName(propName) : undefined, @@ -939,6 +945,8 @@ export abstract class ReactStudioTemplateRenderer extends StudioTemplateRenderer const { bindingProperties } = binding; if ('predicate' in bindingProperties && bindingProperties.predicate !== undefined) { this.importCollection.addMappedImport(ImportValue.USE_DATA_STORE_BINDING); + const modelName = this.importCollection.addImport(ImportSource.LOCAL_MODELS, bindingProperties.model); + /* const buttonColorFilter = { * field: "userID", * operand: "user@email.com", @@ -946,10 +954,7 @@ export abstract class ReactStudioTemplateRenderer extends StudioTemplateRenderer * } */ statements.push(this.buildPredicateDeclaration(propName, bindingProperties.predicate)); - statements.push(this.buildCreateDataStorePredicateCall(bindingProperties.model, propName)); - const { model } = bindingProperties; - this.importCollection.addImport(ImportSource.LOCAL_MODELS, model); - + statements.push(this.buildCreateDataStorePredicateCall(modelName, propName)); /** * const buttonColorDataStore = useDataStoreBinding({ * type: "collection" @@ -967,7 +972,7 @@ export abstract class ReactStudioTemplateRenderer extends StudioTemplateRenderer undefined, factory.createElementAccessExpression( factory.createPropertyAccessExpression( - this.buildUseDataStoreBindingCall('collection', model, this.getFilterName(propName)), + this.buildUseDataStoreBindingCall('collection', modelName, this.getFilterName(propName)), factory.createIdentifier('items'), ), factory.createNumericLiteral('0'), @@ -1090,12 +1095,12 @@ export abstract class ReactStudioTemplateRenderer extends StudioTemplateRenderer Object.entries(this.dataSchema.models[model].fields).forEach(([key, field]) => { if (field.relationship?.type === 'HAS_MANY') { const { relatedModelName, relatedModelField } = field.relationship; - this.importCollection.addImport(ImportSource.LOCAL_MODELS, relatedModelName); + const modelName = this.importCollection.addImport(ImportSource.LOCAL_MODELS, relatedModelName); const itemsName = getActionIdentifier(relatedModelName, 'Items'); statements.push( buildBaseCollectionVariableStatement( factory.createIdentifier(itemsName), - this.buildUseDataStoreBindingCall('collection', relatedModelName), + this.buildUseDataStoreBindingCall('collection', modelName), ), ); propAssigments.push(buildPropAssignmentWithFilter(key, itemsName, relatedModelField)); diff --git a/packages/codegen-ui-react/lib/workflow/action.ts b/packages/codegen-ui-react/lib/workflow/action.ts index 040e95c12..bd1d2da90 100644 --- a/packages/codegen-ui-react/lib/workflow/action.ts +++ b/packages/codegen-ui-react/lib/workflow/action.ts @@ -207,8 +207,8 @@ export function getActionParameterValue( importCollection: ImportCollection, ): Expression { if (key === 'model') { - importCollection.addImport(ImportSource.LOCAL_MODELS, value as string); - return factory.createIdentifier(value as string); + const modelName = importCollection.addImport(ImportSource.LOCAL_MODELS, value as string); + return factory.createIdentifier(modelName); } if (key === 'fields') { return factory.createObjectLiteralExpression( diff --git a/packages/codegen-ui/example-schemas/bindings/data/dataBindingNamedFlex.json b/packages/codegen-ui/example-schemas/bindings/data/dataBindingNamedFlex.json new file mode 100644 index 000000000..270316b04 --- /dev/null +++ b/packages/codegen-ui/example-schemas/bindings/data/dataBindingNamedFlex.json @@ -0,0 +1,29 @@ +{ + "id": "1234-5678-9010", + "componentType": "Text", + "name": "DataBindingNamedClass", + "bindingProperties": { + "class": { + "type": "Data", + "bindingProperties": { + "model": "Flex" + } + } + }, + "properties": { + "label": { + "bindingProperties": { + "property": "class", + "field": "name" + } + } + }, + "children": [ + { + "componentType": "Flex", + "name": "FlexChild", + "properties": {} + } + ], + "schemaVersion": "1.0" +} \ No newline at end of file diff --git a/packages/codegen-ui/example-schemas/collectionWithModelNameCollisions.json b/packages/codegen-ui/example-schemas/collectionWithModelNameCollisions.json new file mode 100644 index 000000000..8d8fb742f --- /dev/null +++ b/packages/codegen-ui/example-schemas/collectionWithModelNameCollisions.json @@ -0,0 +1,90 @@ +{ + "id": "1234-5678-9010", + "componentType": "Collection", + "name": "CollectionWithModelNameCollisions", + "properties": { + "type": { + "value": "list" + } + }, + "bindingProperties": { + "backgroundColor": { + "type": "String" + }, + "buttonColor": { + "type": "Data", + "bindingProperties": { + "model": "Flex", + "predicate": { + "field": "userID", + "operand": "user@email.com", + "operator": "eq" + } + } + }, + "buttonShape": { + "type": "Data", + "bindingProperties": { + "model": "FlexModel", + "predicate": { + "field": "userID", + "operand": "user@email.com", + "operator": "eq" + } + } + } + }, + "collectionProperties": { + "buttonModel": { + "model": "Button", + "sort": [ + { + "field": "lastName", + "direction": "ASC" + } + ], + "predicate": { + "and": [ + { + "field": "age", + "operand": "10", + "operator": "gt" + }, + { + "field": "lastName", + "operand": "L", + "operator": "beginsWith" + } + ] + } + } + }, + "children": [ + { + "componentType": "Flex", + "name": "MyFlex", + "properties": {}, + "children": [ + { + "componentType": "Button", + "name": "MyButton", + "properties": { + "backgroundColor": { + "bindingProperties": { + "property": "buttonColor", + "field": "favoriteColor" + } + }, + "children": { + "collectionBindingProperties": { + "property": "buttonModel", + "field": "lastName" + } + } + } + } + ] + } + ], + "schemaVersion": "1.0" +} \ No newline at end of file diff --git a/packages/test-generator/lib/components/collections/collectionWithSort.json b/packages/test-generator/lib/components/collections/collectionWithSort.json index 449764ab2..686d3842f 100644 --- a/packages/test-generator/lib/components/collections/collectionWithSort.json +++ b/packages/test-generator/lib/components/collections/collectionWithSort.json @@ -87,4 +87,4 @@ } ], "schemaVersion": "1.0" -} +} \ No newline at end of file diff --git a/scripts/integ-test.sh b/scripts/integ-test.sh index 7e3e4af34..131689ab8 100755 --- a/scripts/integ-test.sh +++ b/scripts/integ-test.sh @@ -2,6 +2,6 @@ (cd packages/integration-test && ( (npm start &> /dev/null & npx --no-install wait-on http://localhost:3000) && - npx --no-install cypress run -C cypress.config.ts + TZ=UTC npx --no-install cypress run -C cypress.config.ts kill -9 `lsof -t -i :3000 -s` ))