From 52cd13888046e54e5b843ab823a1e7ce2c4386d7 Mon Sep 17 00:00:00 2001 From: David Lopez Date: Mon, 20 Nov 2023 13:40:34 -0800 Subject: [PATCH] fix: init v6 integ tests --- .eslintrc.js | 3 + .github/workflows/check.yml | 36 +- package.json | 7 + .../codegen-ui-golden-files/package-lock.json | 100 - .../.env | 1 + .../.eslintrc.js | 6 + .../config-overrides.js | 32 + .../cypress.config.ts | 29 + .../cypress/.eslintrc.js | 3 + .../cypress/e2e/action-binding-spec.cy.ts | 174 ++ .../cypress/e2e/complex-spec.cy.ts | 1160 ++++++++++ .../e2e/form/BidirectionalDog-spec.cy.ts | 64 + .../e2e/form/BidirectionalOwner-spec.cy.ts | 65 + .../e2e/form/BidirectionalToy-spec.cy.ts | 44 + .../cypress/e2e/form/CustomDog-spec.cy.ts | 93 + .../e2e/form/CustomNestedJSON-spec.cy.ts | 39 + .../form/DSAllSupportedFormFields-spec.cy.ts | 491 ++++ .../cypress/e2e/form/DSCPKTeacher-spec.cy.ts | 109 + .../cypress/e2e/form/DSCar-spec.cy.ts | 46 + .../e2e/form/DSCompositeDog-spec.cy.ts | 156 ++ .../e2e/form/DSCompositeToy-spec.cy.ts | 47 + .../cypress/e2e/form/DSDealership-spec.cy.ts | 46 + .../ModelWithVariableCollisions-spec.cy.ts | 44 + .../cypress/e2e/generate-spec.cy.ts | 246 ++ .../e2e/generated-components-spec.cy.ts | 413 ++++ .../cypress/e2e/primitives-spec.cy.ts | 391 ++++ .../cypress/e2e/snippet-spec.cy.ts | 22 + .../cypress/e2e/two-way-binding-spec.cy.ts | 225 ++ .../cypress/e2e/workflow-spec.cy.ts | 263 +++ .../scripts/generateCoverageSummary.mjs | 63 + .../cypress/support/e2e.js} | 2 +- .../cypress/tsconfig.json | 5 + .../cypress/utils/form.ts | 58 + .../road-to-milford-new-zealand-800w.jpg | Bin 0 -> 103564 bytes .../src/ActionBindingTests.tsx | 82 + .../src/App.tsx | 100 + .../src/ComplexTests.tsx | 86 + .../src/ComponentTests.tsx | 461 ++++ .../src/FormTests/CustomDog.tsx | 52 + .../src/FormTests/CustomNestedJSON.tsx | 28 + .../FormTests/DSAllSupportedFormFields.tsx | 256 +++ .../src/FormTests/DSBidirectionalDog.tsx | 112 + .../src/FormTests/DSBidirectionalOwner.tsx | 114 + .../src/FormTests/DSBidirectionalToy.tsx | 106 + .../src/FormTests/DSCPKTeacher.tsx | 172 ++ .../src/FormTests/DSCar.tsx | 96 + .../src/FormTests/DSCompositeDog.tsx | 219 ++ .../src/FormTests/DSCompositeToy.tsx | 90 + .../src/FormTests/DSDealership.tsx | 93 + .../DSModelWithVariableCollisions.tsx | 71 + .../src/FormTests/index.tsx | 104 + .../src/GenerateTests.css | 6 + .../src/GenerateTests.tsx | 159 ++ .../src/PrimitivesTests.tsx | 254 ++ .../src/TwoWayBindingTests.tsx | 41 + .../src/WorkflowTests.tsx | 246 ++ .../src/mock-utils.ts | 117 + .../src/models/index.d.ts | 1027 +++++++++ .../src/models/index.js | 93 + .../src/models/schema.d.ts | 18 + .../src/models/schema.js | 2044 +++++++++++++++++ .../src/test-utils.ts | 32 + .../primitives/ExpanderPrimitive.json | 48 - .../components/primitives/TabsPrimitive.json | 33 - .../lib/components/primitives/index.ts | 2 - .../lib/generators/TestGenerator.ts | 31 - packages/test-generator/lib/index.ts | 1 - .../listing-expander-with-component-slot.json | 47 - scripts/integ-setupv6.bat | 32 + scripts/integ-setupv6.sh | 40 + scripts/integ-templatesv6.bat | 4 + scripts/integ-templatesv6.sh | 6 + 72 files changed, 10742 insertions(+), 264 deletions(-) create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/.env create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/.eslintrc.js create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/config-overrides.js create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/cypress.config.ts create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/cypress/.eslintrc.js create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/action-binding-spec.cy.ts create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/complex-spec.cy.ts create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/form/BidirectionalDog-spec.cy.ts create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/form/BidirectionalOwner-spec.cy.ts create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/form/BidirectionalToy-spec.cy.ts create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/form/CustomDog-spec.cy.ts create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/form/CustomNestedJSON-spec.cy.ts create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/form/DSAllSupportedFormFields-spec.cy.ts create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/form/DSCPKTeacher-spec.cy.ts create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/form/DSCar-spec.cy.ts create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/form/DSCompositeDog-spec.cy.ts create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/form/DSCompositeToy-spec.cy.ts create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/form/DSDealership-spec.cy.ts create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/form/ModelWithVariableCollisions-spec.cy.ts create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/generate-spec.cy.ts create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/generated-components-spec.cy.ts create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/primitives-spec.cy.ts create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/snippet-spec.cy.ts create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/two-way-binding-spec.cy.ts create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/workflow-spec.cy.ts create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/cypress/scripts/generateCoverageSummary.mjs rename packages/test-generator/{lib/views/index.ts => integration-test-templates-amplify-js-v6/cypress/support/e2e.js} (85%) create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/cypress/tsconfig.json create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/cypress/utils/form.ts create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/public/road-to-milford-new-zealand-800w.jpg create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/src/ActionBindingTests.tsx create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/src/App.tsx create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/src/ComplexTests.tsx create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/src/ComponentTests.tsx create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/src/FormTests/CustomDog.tsx create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/src/FormTests/CustomNestedJSON.tsx create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/src/FormTests/DSAllSupportedFormFields.tsx create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/src/FormTests/DSBidirectionalDog.tsx create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/src/FormTests/DSBidirectionalOwner.tsx create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/src/FormTests/DSBidirectionalToy.tsx create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/src/FormTests/DSCPKTeacher.tsx create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/src/FormTests/DSCar.tsx create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/src/FormTests/DSCompositeDog.tsx create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/src/FormTests/DSCompositeToy.tsx create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/src/FormTests/DSDealership.tsx create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/src/FormTests/DSModelWithVariableCollisions.tsx create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/src/FormTests/index.tsx create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/src/GenerateTests.css create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/src/GenerateTests.tsx create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/src/PrimitivesTests.tsx create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/src/TwoWayBindingTests.tsx create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/src/WorkflowTests.tsx create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/src/mock-utils.ts create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/src/models/index.d.ts create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/src/models/index.js create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/src/models/schema.d.ts create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/src/models/schema.js create mode 100644 packages/test-generator/integration-test-templates-amplify-js-v6/src/test-utils.ts delete mode 100644 packages/test-generator/lib/components/primitives/ExpanderPrimitive.json delete mode 100644 packages/test-generator/lib/components/primitives/TabsPrimitive.json delete mode 100644 packages/test-generator/lib/views/listing-expander-with-component-slot.json create mode 100644 scripts/integ-setupv6.bat create mode 100755 scripts/integ-setupv6.sh create mode 100644 scripts/integ-templatesv6.bat create mode 100755 scripts/integ-templatesv6.sh diff --git a/.eslintrc.js b/.eslintrc.js index 81a7a65ff..d7893beff 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -8,6 +8,7 @@ module.exports = { 'commitlint.config.js', 'packages/integration-test', '*.md', + '*.css', ], extends: [ 'plugin:@typescript-eslint/recommended', @@ -21,6 +22,8 @@ module.exports = { tsconfigRootDir: __dirname, sourceType: 'module', ecmaVersion: 6, + //for the css file in test-generator/integration-test-templates/src/GenerateTests.css. + // extraFileExtensions: ['.css'] }, rules: { 'max-len': ['error', 120, 2], diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 24185bd30..0cd904215 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -198,7 +198,7 @@ jobs: name: cypress-videos path: /home/runner/work/amplify-codegen-ui/amplify-codegen-ui/e2e-test-app-v6/cypress/videos - functional-tests: + functional-tests-v5: runs-on: ubuntu-latest steps: - name: Checkout Studio Codegen @@ -231,3 +231,37 @@ jobs: - name: Check Integ Test Coverage working-directory: packages/integration-test run: node cypress/scripts/generateCoverageSummary.mjs + + functional-tests-v6: + runs-on: ubuntu-latest + steps: + - name: Checkout Studio Codegen + uses: actions/checkout@v2 + - name: Setup Node.js LTS + uses: actions/setup-node@v2 + with: + node-version: lts/* + - name: Install packages + run: npm ci + - name: Lerna bootstrap + run: npm run bootstrap + - name: Setup Integration Test + run: npm run integ:setupv6 + - name: Cypress run + uses: cypress-io/github-action@v5 + with: + working-directory: packages/integration-test + install: false + start: npm start + wait-on: 'http://localhost:3000' + wait-on-timeout: 210 + config-file: cypress.config.ts + - name: Upload Cypress videos + if: failure() + uses: actions/upload-artifact@v3 + with: + name: cypress-videos + path: /home/runner/work/amplify-codegen-ui/amplify-codegen-ui/packages/integration-test/cypress/videos + - name: Check Integ Test Coverage + working-directory: packages/integration-test + run: node cypress/scripts/generateCoverageSummary.mjs diff --git a/package.json b/package.json index fc5ad20df..c4010091f 100644 --- a/package.json +++ b/package.json @@ -24,10 +24,17 @@ "integ:setup": "run-script-os", "integ:setup:default": "./scripts/integ-setup.sh", "integ:setup:win32": "scripts\\integ-setup.bat", + "integ:setupv6": "run-script-os", + "integ:setupv6:default": "./scripts/integ-setupv6.sh", + "integ:setupv6:win32": "scripts\\integ-setupv6.bat", "integ:templates": "run-script-os", "integ:templates:default": "./scripts/integ-templates.sh", "integ:templates:win32": "scripts\\integ-templates.bat", "integ:templates:watch": "nodemon --watch packages/test-generator/integration-test-templates/ --watch packages/test-generator/lib -e tsx,ts,js,json --exec 'npm run integ:templates'", + "integ:templatesv6": "run-script-os", + "integ:templatesv6:default": "./scripts/integ-templatesv6.sh", + "integ:templatesv6:win32": "scripts\\integ-templatesv6.bat", + "integ:templatesv6:watch": "nodemon --watch packages/test-generator/integration-test-templates/ --watch packages/test-generator/lib -e tsx,ts,js,json --exec 'npm run integ:templates'", "integ:test": "run-script-os", "integ:test:default": "./scripts/integ-test.sh", "integ:test:win32": "scripts\\integ-test.bat", diff --git a/packages/codegen-ui-golden-files/package-lock.json b/packages/codegen-ui-golden-files/package-lock.json index 169ff1a62..4859cb464 100644 --- a/packages/codegen-ui-golden-files/package-lock.json +++ b/packages/codegen-ui-golden-files/package-lock.json @@ -9,7 +9,6 @@ "version": "2.19.4", "license": "Apache-2.0", "dependencies": { - "@aws-amplify/codegen-ui": "2.19.4", "@aws-amplify/datastore": "^3.12.12", "@aws-amplify/ui-react": "^3.5.7", "aws-amplify": "^4.3.37", @@ -113,15 +112,6 @@ "@aws-amplify/core": "4.7.6" } }, - "node_modules/@aws-amplify/codegen-ui": { - "version": "2.19.1", - "resolved": "https://registry.npmjs.org/@aws-amplify/codegen-ui/-/codegen-ui-2.19.1.tgz", - "integrity": "sha512-tmjJr3XXWpRSiqBbBrPg+PlYRafJL8Uk48e424scMsfmlmIIJbeb/FTcwi8aB7rihvgFwe/Z4pCS5OU9gnBciQ==", - "dependencies": { - "change-case": "^4.1.2", - "yup": "^0.32.11" - } - }, "node_modules/@aws-amplify/core": { "version": "4.7.6", "resolved": "https://registry.npmjs.org/@aws-amplify/core/-/core-4.7.6.tgz", @@ -9429,11 +9419,6 @@ "@types/istanbul-lib-report": "*" } }, - "node_modules/@types/lodash": { - "version": "4.14.200", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.200.tgz", - "integrity": "sha512-YI/M/4HRImtNf3pJgbF+W6FrXovqj+T+/HpENLTooK9PnkacBsDpeP3IpHab40CClUfhNmdM2WTNP2sa2dni5Q==" - }, "node_modules/@types/mapbox__point-geometry": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/mapbox__point-geometry/-/mapbox__point-geometry-0.1.2.tgz", @@ -12397,11 +12382,6 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, - "node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" - }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -13178,11 +13158,6 @@ "resolved": "https://registry.npmjs.org/murmurhash-js/-/murmurhash-js-1.0.0.tgz", "integrity": "sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==" }, - "node_modules/nanoclone": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/nanoclone/-/nanoclone-0.2.1.tgz", - "integrity": "sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA==" - }, "node_modules/nanomatch": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", @@ -13902,11 +13877,6 @@ "node": ">= 6" } }, - "node_modules/property-expr": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz", - "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==" - }, "node_modules/protocol-buffers-schema": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz", @@ -15377,11 +15347,6 @@ "node": ">=0.6" } }, - "node_modules/toposort": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", - "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==" - }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -15948,23 +15913,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/yup": { - "version": "0.32.11", - "resolved": "https://registry.npmjs.org/yup/-/yup-0.32.11.tgz", - "integrity": "sha512-Z2Fe1bn+eLstG8DRR6FTavGD+MeAwyfmouhHsIUgaADz8jvFKbO/fXc2trJKZg+5EBjh4gGm3iU/t3onKlXHIg==", - "dependencies": { - "@babel/runtime": "^7.15.4", - "@types/lodash": "^4.14.175", - "lodash": "^4.17.21", - "lodash-es": "^4.17.21", - "nanoclone": "^0.2.1", - "property-expr": "^2.0.4", - "toposort": "^2.0.2" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/zen-observable": { "version": "0.8.15", "resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz", @@ -16092,15 +16040,6 @@ "@aws-amplify/core": "4.7.6" } }, - "@aws-amplify/codegen-ui": { - "version": "2.19.1", - "resolved": "https://registry.npmjs.org/@aws-amplify/codegen-ui/-/codegen-ui-2.19.1.tgz", - "integrity": "sha512-tmjJr3XXWpRSiqBbBrPg+PlYRafJL8Uk48e424scMsfmlmIIJbeb/FTcwi8aB7rihvgFwe/Z4pCS5OU9gnBciQ==", - "requires": { - "change-case": "^4.1.2", - "yup": "^0.32.11" - } - }, "@aws-amplify/core": { "version": "4.7.6", "resolved": "https://registry.npmjs.org/@aws-amplify/core/-/core-4.7.6.tgz", @@ -23963,11 +23902,6 @@ "@types/istanbul-lib-report": "*" } }, - "@types/lodash": { - "version": "4.14.200", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.200.tgz", - "integrity": "sha512-YI/M/4HRImtNf3pJgbF+W6FrXovqj+T+/HpENLTooK9PnkacBsDpeP3IpHab40CClUfhNmdM2WTNP2sa2dni5Q==" - }, "@types/mapbox__point-geometry": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/mapbox__point-geometry/-/mapbox__point-geometry-0.1.2.tgz", @@ -26280,11 +26214,6 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, - "lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" - }, "lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -26947,11 +26876,6 @@ "resolved": "https://registry.npmjs.org/murmurhash-js/-/murmurhash-js-1.0.0.tgz", "integrity": "sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==" }, - "nanoclone": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/nanoclone/-/nanoclone-0.2.1.tgz", - "integrity": "sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA==" - }, "nanomatch": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", @@ -27502,11 +27426,6 @@ "sisteransi": "^1.0.5" } }, - "property-expr": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz", - "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==" - }, "protocol-buffers-schema": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz", @@ -28668,11 +28587,6 @@ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "peer": true }, - "toposort": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", - "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==" - }, "tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -29097,20 +29011,6 @@ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "peer": true }, - "yup": { - "version": "0.32.11", - "resolved": "https://registry.npmjs.org/yup/-/yup-0.32.11.tgz", - "integrity": "sha512-Z2Fe1bn+eLstG8DRR6FTavGD+MeAwyfmouhHsIUgaADz8jvFKbO/fXc2trJKZg+5EBjh4gGm3iU/t3onKlXHIg==", - "requires": { - "@babel/runtime": "^7.15.4", - "@types/lodash": "^4.14.175", - "lodash": "^4.17.21", - "lodash-es": "^4.17.21", - "nanoclone": "^0.2.1", - "property-expr": "^2.0.4", - "toposort": "^2.0.2" - } - }, "zen-observable": { "version": "0.8.15", "resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz", diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/.env b/packages/test-generator/integration-test-templates-amplify-js-v6/.env new file mode 100644 index 000000000..6f809cc25 --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/.env @@ -0,0 +1 @@ +SKIP_PREFLIGHT_CHECK=true diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/.eslintrc.js b/packages/test-generator/integration-test-templates-amplify-js-v6/.eslintrc.js new file mode 100644 index 000000000..2efc0338c --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/.eslintrc.js @@ -0,0 +1,6 @@ +module.exports = { + rules: { + 'import/no-unresolved': 'off', + 'import/no-extraneous-dependencies': 'off', + }, +}; diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/config-overrides.js b/packages/test-generator/integration-test-templates-amplify-js-v6/config-overrides.js new file mode 100644 index 000000000..38926bce3 --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/config-overrides.js @@ -0,0 +1,32 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +module.exports = function override(config) { + return { + ...config, + resolve: { + ...config.resolve, + fallback: { + ...config.resolve.fallback, + os: require.resolve('os-browserify/browser'), + path: require.resolve('path-browserify'), + fs: false, + perf_hooks: false, + }, + }, + // Avoid 'Critical dependency: the request of a dependency is an expression' warning from typescript + ignoreWarnings: [{ module: /node_modules/ }], + }; +}; diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/cypress.config.ts b/packages/test-generator/integration-test-templates-amplify-js-v6/cypress.config.ts new file mode 100644 index 000000000..0dbc87039 --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/cypress.config.ts @@ -0,0 +1,29 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { defineConfig } from 'cypress'; + +export default defineConfig({ + e2e: { + setupNodeEvents(on, config) { + // eslint-disable-next-line global-require, @typescript-eslint/no-var-requires + require('@cypress/code-coverage/task')(on, config); + + return config; + }, + }, + chromeWebSecurity: false, + defaultCommandTimeout: 60000, +}); diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/.eslintrc.js b/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/.eslintrc.js new file mode 100644 index 000000000..eeacb5820 --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: ['plugin:cypress/recommended'], +}; diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/action-binding-spec.cy.ts b/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/action-binding-spec.cy.ts new file mode 100644 index 000000000..59625ba1a --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/action-binding-spec.cy.ts @@ -0,0 +1,174 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +describe('Action Bindings', () => { + describe('Mutation Bindings', () => { + beforeEach(() => { + cy.visit('http://localhost:3000/action-binding-tests'); + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(3000); + }); + + it('supports value bindings', () => { + cy.get('#mutated-value').contains('Fixed Value').should('not.exist'); + cy.contains('Apply Fixed Property Mutation').click(); + cy.get('#mutated-value').contains('Fixed Value'); + }); + + it('supports bound prop bindings', () => { + cy.get('#mutated-value').contains('Bound Value').should('not.exist'); + cy.contains('Current Binding - Bound Value'); + cy.contains('Apply Bound Property Mutation').click(); + cy.get('#mutated-value').contains('Bound Value'); + }); + + it('supports concat bindings', () => { + cy.get('#mutated-value').contains('Concatenated Value').should('not.exist'); + cy.contains('Apply Concatenated Property Mutation').click(); + cy.get('#mutated-value').contains('Concatenated Value'); + }); + + it('supports conditional bindings', () => { + cy.get('#mutated-value').contains('Conditional Value').should('not.exist'); + cy.contains('Apply Conditional Property Mutation').click(); + cy.get('#mutated-value').contains('Conditional Value'); + }); + + it('supports auth bindings', () => { + cy.get('#mutated-value').contains('Auth Value').should('not.exist'); + cy.contains('Apply Auth Property Mutation').click(); + cy.get('#mutated-value').contains('Auth Value'); + }); + + it('supports state bindings', () => { + cy.get('#mutated-value').contains('State Value').should('not.exist'); + cy.contains('Apply State Property Mutation').click(); + cy.get('#mutated-value').contains('State Value'); + }); + }); + + describe('DataStore Bindings', () => { + beforeEach(() => { + cy.visit('http://localhost:3000/action-binding-tests'); + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(3000); + }); + + it('supports value bindings', () => { + cy.get('#data-store-value').contains('Fixed Value').should('not.exist'); + cy.contains('Apply Fixed Property DataStoreUpdateItemAction').click(); + cy.get('#data-store-value').contains('Fixed Value'); + }); + + it('supports bound prop bindings', () => { + cy.get('#data-store-value').contains('Bound Value').should('not.exist'); + cy.contains('Apply Bound Property DataStoreUpdateItemAction').click(); + cy.get('#data-store-value').contains('Bound Value'); + }); + + it('supports concat bindings', () => { + cy.get('#data-store-value').contains('Concatenated Value').should('not.exist'); + cy.contains('Apply Concatenated Property DataStoreUpdateItemAction').click(); + cy.get('#data-store-value').contains('Concatenated Value'); + }); + + it('supports conditional bindings', () => { + cy.get('#data-store-value').contains('Conditional Value').should('not.exist'); + cy.contains('Apply Conditional Property DataStoreUpdateItemAction').click(); + cy.get('#data-store-value').contains('Conditional Value'); + }); + + it('supports auth bindings', () => { + cy.get('#data-store-value').contains('Auth Value').should('not.exist'); + cy.contains('Apply Auth Property DataStoreUpdateItemAction').click(); + cy.get('#data-store-value').contains('Auth Value'); + }); + + it('supports state bindings', () => { + cy.get('#data-store-value').contains('State Value').should('not.exist'); + cy.contains('Apply State Property DataStoreUpdateItemAction').click(); + cy.get('#data-store-value').contains('State Value'); + }); + }); + + describe('Initial Value Bindings', () => { + beforeEach(() => { + cy.visit('http://localhost:3000/action-binding-tests'); + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(1000); + }); + + it('supports fixed values', () => { + cy.get('#fixed-value-initial-binding-section').within(() => { + cy.contains('Fixed Value'); + cy.contains('Mutate').click(); + cy.contains('Mutated Value'); + }); + }); + + it('supports bound values', () => { + cy.get('#bound-value-initial-binding-section').within(() => { + cy.contains('Bound Value'); + cy.contains('Mutate').click(); + cy.contains('Mutated Value'); + }); + }); + + it('supports concat values', () => { + cy.get('#concat-value-initial-binding-section').within(() => { + cy.contains('Concat Value'); + cy.contains('Mutate').click(); + cy.contains('Mutated Value'); + }); + }); + + it('supports conditional values', () => { + cy.get('#conditional-value-initial-binding-section').within(() => { + cy.contains('Conditional Value'); + cy.contains('Mutate').click(); + cy.contains('Mutated Value'); + }); + }); + + it('supports auth values', () => { + cy.get('#auth-value-initial-binding-section').within(() => { + cy.contains('Auth Value'); + cy.contains('Mutate').click(); + cy.contains('Mutated Value'); + }); + }); + + it('supports state values', () => { + cy.get('#state-value-initial-binding-section').within(() => { + cy.contains('State Value'); + cy.contains('Mutate').click(); + cy.contains('Mutated Value'); + }); + }); + + it('supports values in text fields', () => { + cy.get('#text-field-value-initial-binding-section').within(() => { + cy.get('input').should('have.attr', 'value', 'Auth Value'); + cy.get('button').click(); + cy.get('input').should('have.attr', 'value', 'Mutated Value'); + cy.get('input').type(' with typing'); + cy.get('input').should('have.attr', 'value', 'Mutated Value with typing'); + cy.get('button').click(); + cy.get('input').should('have.attr', 'value', 'Mutated Value'); + }); + }); + }); +}); diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/complex-spec.cy.ts b/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/complex-spec.cy.ts new file mode 100644 index 000000000..724ffb264 --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/complex-spec.cy.ts @@ -0,0 +1,1160 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +describe('Complex Components', () => { + beforeEach(() => { + cy.visit('http://localhost:3000/complex-tests'); + }); + + it('Complex 1', () => { + cy.get('#complex-test-1').within(() => { + cy.get('.amplify-flex') + .then((el) => { + const style = el.attr('style'); + const expectedStyles = [ + 'align-items: flex-start', + 'background-color: rgb(255, 255, 255)', + 'flex-direction: row', + 'gap: 10px', + 'overflow: hidden', + 'padding: 34px 56px', + 'position: relative', + ]; + expect(style.split('; ')).to.have.length(expectedStyles.length); + expectedStyles.forEach((expected) => { + expect(style).to.include(expected); + }); + }) + .within(() => { + cy.get('.amplify-text').then((el) => { + const style = el.attr('style'); + const expectedStyles = [ + 'color: rgb(0, 0, 0)', + 'font-family: Roboto', + 'font-size: 12px', + 'font-weight: 400', + 'height: 14px', + 'line-height: 14.0625px', + 'padding: 0px', + 'position: relative', + 'text-align: left', + 'width: 52px', + ]; + expect(style.split('; ')).to.have.length(expectedStyles.length); + expectedStyles.forEach((expected) => { + expect(style).to.include(expected); + }); + }); + cy.get('.amplify-text').should('have.text', 'Hi harriso'); + }); + }); + }); + + it('Complex 2', () => { + cy.get('#complex-test-2').within(() => { + cy.get('.amplify-flex') + .first() + .then((el) => { + const style = el.attr('style'); + const expectedStyles = [ + 'align-items: flex-start', + 'background-color: rgb(255, 255, 255)', + 'flex-direction: row', + 'gap: 0px', + 'height: 289px', + 'padding: 0px', + 'position: relative', + 'width: 153px', + ]; + expect(style.split('; ')).to.have.length(expectedStyles.length); + expectedStyles.forEach((expected) => { + expect(style).to.include(expected); + }); + }) + .within(() => { + cy.get('.amplify-flex') + .then((el) => { + const style = el.attr('style'); + const expectedStyles = [ + 'align-self: stretch', + 'flex-direction: column', + 'gap: 10px', + 'height: 100%', + 'overflow: hidden', + 'padding: 10px', + 'position: relative', + 'width: 100%', + ]; + expect(style.split('; ')).to.have.length(expectedStyles.length); + expectedStyles.forEach((expected) => { + expect(style).to.include(expected); + }); + }) + .within(() => { + cy.get('div') + .eq(0) + .then((el) => { + const style = el.attr('style'); + const expectedStyles = [ + 'background-color: rgb(255, 0, 0)', + 'height: 125px', + 'padding: 0px', + 'position: relative', + 'width: 123px', + ]; + expect(style.split('; ')).to.have.length(expectedStyles.length); + expectedStyles.forEach((expected) => { + expect(style).to.include(expected); + }); + }) + .should('have.attr', 'src', 'https://via.placeholder.com/153x289?text=Amplify+Studio+is+Awesome!'); + cy.get('div') + .eq(1) + .then((el) => { + const style = el.attr('style'); + const expectedStyles = [ + 'background-color: rgb(219, 255, 0)', + 'height: 122px', + 'padding: 0px', + 'position: relative', + 'width: 123px', + ]; + expect(style.split('; ')).to.have.length(expectedStyles.length); + expectedStyles.forEach((expected) => { + expect(style).to.include(expected); + }); + }) + .should('have.attr', 'src', 'https://via.placeholder.com/153x289?text=Amplify+Studio+is+Awesome!'); + }); + }); + }); + }); + + it('Complex 3', () => { + cy.get('#complex-test-3').within(() => { + cy.get('.amplify-flex') + .then((el) => { + const style = el.attr('style'); + const expectedStyles = [ + 'align-items: center', + 'background-color: rgb(255, 255, 255)', + 'flex-direction: column', + 'gap: 80px', + 'justify-content: center', + 'overflow: hidden', + 'padding: 20px', + 'position: relative', + ]; + expect(style.split('; ')).to.have.length(expectedStyles.length); + expectedStyles.forEach((expected) => { + expect(style).to.include(expected); + }); + }) + .within(() => { + cy.get('.amplify-text') + .eq(0) + .then((el) => { + const style = el.attr('style'); + const expectedStyles = [ + 'color: rgb(0, 0, 0)', + 'font-family: Inter', + 'font-size: 24px', + 'font-weight: 400', + 'height: 29px', + 'line-height: 28.8px', + 'padding: 0px', + 'position: relative', + 'text-align: left', + 'width: 146px', + ]; + expect(style.split('; ')).to.have.length(expectedStyles.length); + expectedStyles.forEach((expected) => { + expect(style).to.include(expected); + }); + }) + .should('have.text', 'Hello World!'); + + cy.get('div') + .should('have.attr', 'src', 'https://via.placeholder.com/430x452?text=Amplify+Studio+is+Awesome!') + .then((el) => { + const style = el.attr('style'); + const expectedStyles = [ + 'background-color: rgb(196, 196, 196)', + 'height: 69px', + 'padding: 0px', + 'position: relative', + 'width: 390px', + ]; + expect(style.split('; ')).to.have.length(expectedStyles.length); + expectedStyles.forEach((expected) => { + expect(style).to.include(expected); + }); + }); + cy.get('.amplify-button') + .should('have.attr', 'data-fullwidth', 'false') + .should('have.attr', 'type', 'button') + .then((el) => { + const style = el.attr('style'); + const expectedStyles = ['height: 45px', 'width: 293px']; + expect(style.split('; ')).to.have.length(expectedStyles.length); + expectedStyles.forEach((expected) => { + expect(style).to.include(expected); + }); + }); + cy.get('.amplify-text') + .eq(1) + .then((el) => { + const style = el.attr('style'); + const expectedStyles = [ + 'color: rgb(0, 0, 0)', + 'font-family: Inter', + 'font-size: 24px', + 'font-weight: 400', + 'height: 29px', + 'line-height: 28.8px', + 'padding: 0px', + 'position: relative', + 'text-align: left', + 'width: 138px', + ]; + expect(style.split('; ')).to.have.length(expectedStyles.length); + expectedStyles.forEach((expected) => { + expect(style).to.include(expected); + }); + }) + .should('have.text', 'Testing 123'); + }); + }); + }); + + it('Complex 4', () => { + cy.get('#complex-test-4').within(() => { + cy.get('.amplify-flex') + .first() + .then((el) => { + const style = el.attr('style'); + const expectedStyles = [ + 'align-items: flex-start', + 'background-color: rgb(255, 255, 255)', + 'flex-direction: row', + 'gap: 10px', + 'overflow: hidden', + 'padding: 10px', + 'position: relative', + ]; + expect(style.split('; ')).to.have.length(expectedStyles.length); + expectedStyles.forEach((expected) => { + expect(style).to.include(expected); + }); + }) + .within(() => { + cy.get('.amplify-flex') + .then((el) => { + const style = el.attr('style'); + const expectedStyles = ['height: 123px', 'padding: 0px', 'position: relative', 'width: 323px']; + expect(style.split('; ')).to.have.length(expectedStyles.length); + expectedStyles.forEach((expected) => { + expect(style).to.include(expected); + }); + }) + .within(() => { + cy.get('div') + .eq(0) + .should('have.attr', 'src', 'https://via.placeholder.com/323x123?text=Amplify+Studio+is+Awesome!') + .then((el) => { + const style = el.attr('style'); + const expectedStyles = [ + 'background-color: rgb(36, 0, 255)', + 'height: 123px', + 'left: 0px', + 'padding: 0px', + 'position: absolute', + 'top: 0px', + 'width: 122px', + ]; + expect(style.split('; ')).to.have.length(expectedStyles.length); + expectedStyles.forEach((expected) => { + expect(style).to.include(expected); + }); + }); + cy.get('div') + .eq(1) + .should('have.attr', 'src', 'https://via.placeholder.com/323x123?text=Amplify+Studio+is+Awesome!') + .then((el) => { + const style = el.attr('style'); + const expectedStyles = [ + 'background-color: rgb(0, 255, 25)', + 'height: 122px', + 'left: 203px', + 'padding: 0px', + 'position: absolute', + 'top: 1px', + 'width: 120px', + ]; + expect(style.split('; ')).to.have.length(expectedStyles.length); + expectedStyles.forEach((expected) => { + expect(style).to.include(expected); + }); + }); + }); + }); + }); + }); + + it('Complex 5', () => { + cy.get('#complex-test-5').within(() => { + cy.get('.amplify-flex') + .then((el) => { + const style = el.attr('style'); + const expectedStyles = [ + 'align-items: flex-start', + 'background-color: rgb(255, 255, 255)', + 'flex-direction: row', + 'gap: 0px', + 'padding: 0px', + 'position: relative', + ]; + expect(style.split('; ')).to.have.length(expectedStyles.length); + expectedStyles.forEach((expected) => { + expect(style).to.include(expected); + }); + }) + .within(() => { + cy.get('div') + .eq(0) + .should('have.attr', 'src', 'https://via.placeholder.com/240x120?text=Amplify+Studio+is+Awesome!') + .then((el) => { + const style = el.attr('style'); + const expectedStyles = [ + 'background-color: rgb(36, 0, 255)', + 'height: 120px', + 'padding: 0px', + 'position: relative', + 'width: 120px', + ]; + expect(style.split('; ')).to.have.length(expectedStyles.length); + expectedStyles.forEach((expected) => { + expect(style).to.include(expected); + }); + }); + cy.get('div') + .eq(1) + .should('have.attr', 'src', 'https://via.placeholder.com/240x120?text=Amplify+Studio+is+Awesome!') + .then((el) => { + const style = el.attr('style'); + const expectedStyles = [ + 'background-color: rgb(0, 255, 25)', + 'height: 120px', + 'padding: 0px', + 'position: relative', + 'width: 120px', + ]; + expect(style.split('; ')).to.have.length(expectedStyles.length); + expectedStyles.forEach((expected) => { + expect(style).to.include(expected); + }); + }); + }); + }); + }); + + it('Complex 6', () => { + cy.get('#complex-test-6').within(() => { + cy.get('.amplify-flex') + .then((el) => { + const style = el.attr('style'); + const expectedStyles = [ + 'background-color: rgb(255, 255, 255)', + 'flex-direction: column', + 'gap: 22px', + 'overflow: hidden', + 'padding: 21px 42px', + 'position: relative', + ]; + expect(style.split('; ')).to.have.length(expectedStyles.length); + expectedStyles.forEach((expected) => { + expect(style).to.include(expected); + }); + }) + .within(() => { + cy.get('.amplify-text') + .eq(0) + .then((el) => { + const style = el.attr('style'); + const expectedStyles = [ + 'color: rgb(0, 0, 0)', + 'font-family: Inter', + 'font-size: 24px', + 'font-weight: 700', + 'height: 29px', + 'line-height: 28.125px', + 'padding: 0px', + 'position: relative', + 'text-align: left', + 'width: 68px', + ]; + expect(style.split('; ')).to.have.length(expectedStyles.length); + expectedStyles.forEach((expected) => { + expect(style).to.include(expected); + }); + }) + .should('have.text', 'Name'); + + cy.get('.amplify-text') + .eq(1) + .then((el) => { + const style = el.attr('style'); + const expectedStyles = [ + 'color: rgb(0, 0, 0)', + 'font-family: Roboto', + 'font-size: 12px', + 'font-weight: 400', + 'height: 14px', + 'line-height: 14.0625px', + 'padding: 0px', + 'position: relative', + 'text-align: left', + 'width: 83px', + ]; + expect(style.split('; ')).to.have.length(expectedStyles.length); + expectedStyles.forEach((expected) => { + expect(style).to.include(expected); + }); + }) + .should('have.text', 'Price / Address'); + + cy.get('.amplify-text') + .eq(2) + .then((el) => { + const style = el.attr('style'); + const expectedStyles = [ + 'color: rgb(0, 0, 0)', + 'font-family: Roboto', + 'font-size: 12px', + 'font-weight: 400', + 'height: 14px', + 'line-height: 14.0625px', + 'padding: 0px', + 'position: relative', + 'text-align: left', + 'width: 23px', + ]; + expect(style.split('; ')).to.have.length(expectedStyles.length); + expectedStyles.forEach((expected) => { + expect(style).to.include(expected); + }); + }) + .should('have.text', 'Sqft'); + }); + }); + }); + + it('Complex 7', () => { + cy.get('#complex-test-7').within(() => { + cy.get('div') + .then((el) => { + const style = el.attr('style'); + const expectedStyles = ['height: 192px', 'padding: 0px', 'position: relative', 'width: 401px']; + expect(style.split('; ')).to.have.length(expectedStyles.length); + expectedStyles.forEach((expected) => { + expect(style).to.include(expected); + }); + }) + .within(() => { + cy.get('img') + .eq(0) + .then((el) => { + const style = el.attr('style'); + const expectedStyles = [ + 'border: 4px solid rgb(0, 0, 0)', + 'border-radius: 45px', + 'height: 196px', + 'left: 254.151px', + 'padding: 0px', + 'position: absolute', + 'top: 0px', + 'width: 150.849px', + ]; + expect(style.split('; ')).to.have.length(expectedStyles.length); + expectedStyles.forEach((expected) => { + expect(style).to.include(expected); + }); + }) + .should('have.attr', 'src', 'https://via.placeholder.com/401x192?text=Amplify+Studio+is+Awesome!') + .should('have.attr', 'alt', 'Amplify Studio is Awesome!'); + + cy.get('.amplify-text') + .then((el) => { + const style = el.attr('style'); + const expectedStyles = [ + 'color: rgb(0, 0, 0)', + 'font-family: Inter', + 'font-size: 24px', + 'font-weight: 400', + 'height: 27.84px', + 'left: 187.401px', + 'line-height: 28.8px', + 'padding: 0px', + 'position: absolute', + 'text-align: left', + 'top: 0px', + 'width: 54.4267px', + ]; + expect(style.split('; ')).to.have.length(expectedStyles.length); + expectedStyles.forEach((expected) => { + expect(style).to.include(expected); + }); + }) + .should('have.text', 'Test'); + + cy.get('img') + .eq(1) + .then((el) => { + const style = el.attr('style'); + const expectedStyles = [ + 'border: 4px solid rgb(0, 0, 0)', + 'border-radius: 27px', + 'height: 189.088px', + 'left: 0px', + 'padding: 0px', + 'position: absolute', + 'top: 0px', + 'width: 169.423px', + ]; + expect(style.split('; ')).to.have.length(expectedStyles.length); + expectedStyles.forEach((expected) => { + expect(style).to.include(expected); + }); + }) + .should('have.attr', 'src', 'https://via.placeholder.com/401x192?text=Amplify+Studio+is+Awesome!') + .should('have.attr', 'alt', 'Amplify Studio is Awesome!'); + }); + }); + }); + + it('Complex 8', () => { + cy.get('#complex-test-8').within(() => { + cy.get('.amplify-flex') + .first() + .then((el) => { + const style = el.attr('style'); + const expectedStyles = [ + 'align-items: flex-start', + 'background-color: rgb(255, 255, 255)', + 'flex-direction: row', + 'gap: 0px', + 'height: 243px', + 'padding: 0px', + 'position: relative', + 'width: 145px', + ]; + expect(style.split('; ')).to.have.length(expectedStyles.length); + expectedStyles.forEach((expected) => { + expect(style).to.include(expected); + }); + }) + .within(() => { + cy.get('.amplify-flex') + .then((el) => { + const style = el.attr('style'); + const expectedStyles = [ + 'flex-direction: column', + 'gap: 0px', + 'height: 723px', + 'padding: 0px', + 'position: relative', + 'width: 472px', + ]; + expect(style.split('; ')).to.have.length(expectedStyles.length); + expectedStyles.forEach((expected) => { + expect(style).to.include(expected); + }); + }) + .within(() => { + cy.get('div') + .eq(0) + .then((el) => { + const style = el.attr('style'); + const expectedStyles = [ + 'background-color: rgb(219, 255, 0)', + 'height: 119px', + 'padding: 0px', + 'position: relative', + 'width: 145px', + ]; + expect(style.split('; ')).to.have.length(expectedStyles.length); + expectedStyles.forEach((expected) => { + expect(style).to.include(expected); + }); + }) + .should('have.attr', 'src', 'https://via.placeholder.com/472x723?text=Amplify+Studio+is+Awesome!'); + cy.get('div') + .eq(1) + .then((el) => { + const style = el.attr('style'); + const expectedStyles = [ + 'background-color: rgb(255, 0, 0)', + 'height: 124px', + 'padding: 0px', + 'position: relative', + 'width: 145px', + ]; + expect(style.split('; ')).to.have.length(expectedStyles.length); + expectedStyles.forEach((expected) => { + expect(style).to.include(expected); + }); + }) + .should('have.attr', 'src', 'https://via.placeholder.com/472x723?text=Amplify+Studio+is+Awesome!'); + }); + }); + }); + }); + it('Complex 9', () => { + cy.get('#complex-test-9').within(() => { + cy.get('.amplify-flex') + .first() + .within(() => { + cy.get('.amplify-flex') + .then((el) => { + const style = el.attr('style'); + const expectedStyles = [ + 'align-items: center', + 'align-self: stretch', + 'background-color: rgb(0, 64, 77)', + 'flex-basis: 685px', + 'flex-direction: column', + 'gap: 10px', + 'flex-grow: 1', + 'height: 422px', + 'justify-content: center', + 'overflow: hidden', + 'padding: 120px', + 'position: relative', + ]; + expect(style.split('; ')).to.have.length(expectedStyles.length); + expectedStyles.forEach((expected) => { + expect(style).to.include(expected); + }); + }) + .first() + .within(() => { + cy.get('.amplify-flex') + .then((el) => { + const style = el.attr('style'); + const expectedStyles = [ + 'align-items: center', + 'align-self: stretch', + 'flex-direction: column', + 'gap: 24px', + 'justify-content: center', + 'padding: 0px', + 'position: relative', + 'flex-shrink: 0', + ]; + expect(style.split('; ')).to.have.length(expectedStyles.length); + expectedStyles.forEach((expected) => { + expect(style).to.include(expected); + }); + }) + .first() + .within(() => { + cy.get('.amplify-text') + .contains('TestMessage1') + .then((el) => { + const style = el.attr('style'); + const expectedStyles = [ + 'align-self: stretch', + 'color: rgb(233, 249, 252)', + 'flex-direction: column', + 'display: flex', + 'font-family: Inter', + 'font-size: 16px', + 'font-weight: 700', + 'justify-content: flex-start', + 'letter-spacing: 0.49px', + 'line-height: 20px', + 'padding: 0px', + 'position: relative', + 'flex-shrink: 0', + 'text-align: center', + 'width: 445px', + ]; + expect(style.split('; ')).to.have.length(expectedStyles.length); + expectedStyles.forEach((expected) => { + expect(style).to.include(expected); + }); + }); + cy.get('.amplify-button').contains('TestButton1'); + cy.get('.amplify-flex') + .then((el) => { + const style = el.attr('style'); + const expectedStyles = [ + 'align-items: center', + 'align-self: stretch', + 'flex-direction: column', + 'gap: 16px', + 'justify-content: center', + 'padding: 0px', + 'position: relative', + 'flex-shrink: 0', + ]; + expect(style.split('; ')).to.have.length(expectedStyles.length); + expectedStyles.forEach((expected) => { + expect(style).to.include(expected); + }); + }) + .first() + .within(() => { + cy.get('.amplify-text') + .eq(0) + .contains('TestMessage2') + .then((el) => { + const style = el.attr('style'); + const expectedStyles = [ + 'align-self: stretch', + 'color: rgb(233, 249, 252)', + 'flex-direction: column', + 'display: flex', + 'font-family: Inter', + 'font-size: 40px', + 'font-weight: 700', + 'justify-content: flex-start', + 'line-height: 48px', + 'padding: 0px', + 'position: relative', + 'flex-shrink: 0', + 'text-align: center', + 'width: 445px', + ]; + expect(style.split('; ')).to.have.length(expectedStyles.length); + expectedStyles.forEach((expected) => { + expect(style).to.include(expected); + }); + }); + cy.get('.amplify-text') + .eq(1) + .contains('TestMessage3') + .then((el) => { + const style = el.attr('style'); + const expectedStyles = [ + 'align-self: stretch', + 'color: rgb(233, 249, 252)', + 'flex-direction: column', + 'display: flex', + 'font-family: Inter', + 'font-size: 16px', + 'font-weight: 400', + 'justify-content: flex-start', + 'letter-spacing: 0.01px', + 'line-height: 24px', + 'padding: 0px', + 'position: relative', + 'flex-shrink: 0', + 'text-align: center', + 'width: 445px', + ]; + expect(style.split('; ')).to.have.length(expectedStyles.length); + expectedStyles.forEach((expected) => { + expect(style).to.include(expected); + }); + }); + }); + }); + }); + }); + }); + }); + + it('Complex 10', () => { + cy.get('#complex-test-10') + .first() + .within(() => { + cy.get('.amplify-flex') + .then((el) => { + const style = el.attr('style'); + const expectedStyles = [ + 'align-items: center', + 'background-color: rgb(239, 240, 240)', + 'flex-direction: row', + 'gap: 24px', + 'justify-content: center', + 'overflow: hidden', + 'padding: 40px 140px', + 'position: relative', + 'width: 1440px', + ]; + expect(style.split('; ')).to.have.length(expectedStyles.length); + expectedStyles.forEach((expected) => { + expect(style).to.include(expected); + }); + }) + .first() + .within(() => { + cy.get('.amplify-flex') + .then((el) => { + const style = el.attr('style'); + const expectedStyles = [ + 'align-items: flex-start', + 'flex-basis: 1160px', + 'flex-direction: row', + 'gap: 24px', + 'flex-grow: 1', + 'height: 618px', + 'padding: 0px', + 'position: relative', + 'width: 1160px', + ]; + expect(style.split('; ')).to.have.length(expectedStyles.length); + expectedStyles.forEach((expected) => { + expect(style).to.include(expected); + }); + }) + .first() + .within(() => { + cy.get('.amplify-flex') + .eq(0) + .then((el) => { + const style = el.attr('style'); + const expectedStyles = [ + 'align-items: center', + 'background-color: rgb(255, 255, 255)', + 'flex-basis: 272px', + 'flex-direction: column', + 'gap: 24px', + 'flex-grow: 1', + 'height: 618px', + 'justify-content: center', + 'padding: 24px', + 'position: relative', + 'width: 272px', + ]; + expect(style.split('; ')).to.have.length(expectedStyles.length); + expectedStyles.forEach((expected) => { + expect(style).to.include(expected); + }); + }) + .first() + .within(() => { + cy.get('.amplify-text') + .eq(0) + .contains('Free') + .then((el) => { + const style = el.attr('style'); + const expectedStyles = [ + 'align-self: stretch', + 'color: rgb(13, 26, 38)', + 'flex-direction: column', + 'display: flex', + 'font-family: Inter', + 'font-size: 40px', + 'font-weight: 700', + 'justify-content: flex-start', + 'line-height: 48px', + 'padding: 0px', + 'position: relative', + 'flex-shrink: 0', + 'text-align: center', + 'width: 224px', + ]; + expect(style.split('; ')).to.have.length(expectedStyles.length); + expectedStyles.forEach((expected) => { + expect(style).to.include(expected); + }); + }); + cy.get('.amplify-button').contains('Primary Button'); + cy.get('.amplify-flex') + .eq(0) + .then((el) => { + const style = el.attr('style'); + const expectedStyles = [ + 'align-items: flex-start', + 'align-self: stretch', + 'flex-direction: row', + 'gap: 16px', + 'padding: 0px', + 'position: relative', + 'flex-shrink: 0', + ]; + expect(style.split('; ')).to.have.length(expectedStyles.length); + expectedStyles.forEach((expected) => { + expect(style).to.include(expected); + }); + }) + .first() + .within(() => { + cy.get('.amplify-icon').then((el) => { + const style = el.attr('style'); + const expectedStyles = [ + 'color: rgb(64, 170, 191)', + 'font-size: 24px', + 'height: 24px', + 'overflow: hidden', + 'padding: 0px', + 'position: relative', + 'flex-shrink: 0', + 'width: 24px', + ]; + expect(style.split('; ')).to.have.length(expectedStyles.length); + expectedStyles.forEach((expected) => { + expect(style).to.include(expected); + }); + }); + cy.get('.amplify-text') + .then((el) => { + const style = el.attr('style'); + const expectedStyles = [ + 'flex-basis: 184px', + 'color: rgb(48, 64, 80)', + 'flex-direction: column', + 'display: flex', + 'font-family: Inter', + 'font-size: 16px', + 'font-weight: 400', + 'flex-grow: 1', + 'justify-content: flex-start', + 'letter-spacing: 0.01px', + 'line-height: 24px', + 'padding: 0px', + 'position: relative', + 'text-align: left', + 'width: 184px', + ]; + expect(style.split('; ')).to.have.length(expectedStyles.length); + expectedStyles.forEach((expected) => { + expect(style).to.include(expected); + }); + }) + .contains('Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.'); + }); + }); + cy.get('.amplify-flex') + .eq(12) + .then((el) => { + const style = el.attr('style'); + const expectedStyles = [ + 'align-items: center', + 'background-color: rgb(255, 255, 255)', + 'flex-basis: 272px', + 'flex-direction: column', + 'gap: 24px', + 'flex-grow: 1', + 'height: 618px', + 'justify-content: center', + 'padding: 24px', + 'position: relative', + 'width: 272px', + ]; + expect(style.split('; ')).to.have.length(expectedStyles.length); + expectedStyles.forEach((expected) => { + expect(style).to.include(expected); + }); + }) + .first() + .within(() => { + cy.get('.amplify-text') + .eq(0) + .contains('Enterprise') + .then((el) => { + const style = el.attr('style'); + const expectedStyles = [ + 'align-self: stretch', + 'color: rgb(13, 26, 38)', + 'flex-direction: column', + 'display: flex', + 'font-family: Inter', + 'font-size: 40px', + 'font-weight: 700', + 'justify-content: flex-start', + 'line-height: 48px', + 'padding: 0px', + 'position: relative', + 'flex-shrink: 0', + 'text-align: center', + 'width: 224px', + ]; + expect(style.split('; ')).to.have.length(expectedStyles.length); + expectedStyles.forEach((expected) => { + expect(style).to.include(expected); + }); + }); + cy.get('.amplify-button').contains('Primary Button'); + cy.get('.amplify-flex') + .eq(0) + .then((el) => { + const style = el.attr('style'); + const expectedStyles = [ + 'align-items: flex-start', + 'align-self: stretch', + 'flex-direction: row', + 'gap: 16px', + 'padding: 0px', + 'position: relative', + 'flex-shrink: 0', + ]; + expect(style.split('; ')).to.have.length(expectedStyles.length); + expectedStyles.forEach((expected) => { + expect(style).to.include(expected); + }); + }) + .first() + .within(() => { + cy.get('.amplify-icon').then((el) => { + const style = el.attr('style'); + const expectedStyles = [ + 'color: rgb(64, 170, 191)', + 'font-size: 24px', + 'height: 24px', + 'overflow: hidden', + 'padding: 0px', + 'position: relative', + 'flex-shrink: 0', + 'width: 24px', + ]; + expect(style.split('; ')).to.have.length(expectedStyles.length); + expectedStyles.forEach((expected) => { + expect(style).to.include(expected); + }); + }); + cy.get('.amplify-text') + .then((el) => { + const style = el.attr('style'); + const expectedStyles = [ + 'flex-basis: 184px', + 'color: rgb(48, 64, 80)', + 'flex-direction: column', + 'display: flex', + 'font-family: Inter', + 'font-size: 16px', + 'font-weight: 400', + 'flex-grow: 1', + 'justify-content: flex-start', + 'letter-spacing: 0.01px', + 'line-height: 24px', + 'padding: 0px', + 'position: relative', + 'text-align: left', + 'width: 184px', + ]; + expect(style.split('; ')).to.have.length(expectedStyles.length); + expectedStyles.forEach((expected) => { + expect(style).to.include(expected); + }); + }) + .contains( + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor123123.', + ); + }); + }); + }); + }); + }); + }); + + it('Complex 11', () => { + cy.get('#complex-test-11') + .first() + .within(() => { + cy.get('.amplify-flex') + .then((el) => { + const style = el.attr('style'); + const expectedStyles = [ + 'align-items: flex-start', + 'flex-direction: row', + 'gap: 24px', + 'padding: 0px', + 'position: relative', + 'width: 1160px', + ]; + expect(style.split('; ')).to.have.length(expectedStyles.length); + expectedStyles.forEach((expected) => { + expect(style).to.include(expected); + }); + }) + .first() + .within(() => { + cy.get('.amplify-flex').then((el) => { + const style = el.attr('style'); + const expectedStyles = [ + 'align-items: flex-start', + 'background-color: rgb(255, 255, 255)', + 'flex-basis: 667px', + 'flex-direction: row', + 'gap: 0px', + 'flex-grow: 1', + 'height: 1148px', + 'padding: 32px 0px', + 'position: relative', + 'width: 667px', + ]; + expect(style.split('; ')).to.have.length(expectedStyles.length); + expectedStyles.forEach((expected) => { + expect(style).to.include(expected); + }); + }); + cy.get('.amplify-button') + .contains('Place Order') + .then((el) => { + const style = el.attr('style'); + const expectedStyles = [ + 'display: flex', + 'left: 32px', + 'position: absolute', + 'top: 822px', + 'width: 405px', + ]; + expect(style.split('; ')).to.have.length(expectedStyles.length); + expectedStyles.forEach((expected) => { + expect(style).to.include(expected); + }); + }); + cy.get('.amplify-badge') + .contains('Discount - 10% off') + .then((el) => { + const style = el.attr('style'); + const expectedStyles = [ + 'background-color: rgb(214, 245, 219)', + 'color: rgb(54, 94, 61)', + 'flex-direction: column', + 'display: flex', + 'font-family: Inter', + 'font-size: 16px', + 'font-weight: 700', + 'justify-content: flex-start', + 'left: 32px', + 'letter-spacing: 0.49px', + 'line-height: 20px', + 'position: absolute', + 'text-align: left', + 'top: 0px', + 'width: 405px', + ]; + expect(style.split('; ')).to.have.length(expectedStyles.length); + expectedStyles.forEach((expected) => { + expect(style).to.include(expected); + }); + }); + cy.get('.amplify-flex') + .first() + .within(() => { + cy.get('.amplify-flex') + .first() + .within(() => { + cy.get('.amplify-label').contains('Label'); + }); + }); + }); + }); + }); +}); diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/form/BidirectionalDog-spec.cy.ts b/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/form/BidirectionalDog-spec.cy.ts new file mode 100644 index 000000000..25cbf3812 --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/form/BidirectionalDog-spec.cy.ts @@ -0,0 +1,64 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import { clickAddToArray, getArrayFieldButtonByLabel, removeArrayItem, typeInAutocomplete } from '../../utils/form'; + +describe('FormTests - DSBidirectionalDog', () => { + beforeEach(() => { + cy.visit('http://localhost:3000/form-tests/DSBidirectionalDog'); + }); + + specify( + 'Update form for child of bidirectional 1:1 should throw when disconnecting parent if parent requires child', + () => { + cy.get('#DataStoreFormUpdateBidirectionalDog').within(() => { + removeArrayItem('Fluffys Owner'); + + cy.contains('Submit').click(); + cy.contains('cannot be unlinked because BiDirectionalOwner requires BiDirectionalDog'); + }); + }, + ); + + specify( + 'Update form for child of bidirectional 1:1 should throw when changing parent if parent requires child', + () => { + cy.get('#DataStoreFormUpdateBidirectionalDog').within(() => { + removeArrayItem('Fluffys Owner'); + getArrayFieldButtonByLabel('Bi directional owner').click(); + typeInAutocomplete('M{downArrow}{enter}'); + clickAddToArray(); + + cy.contains('Submit').click(); + cy.contains('cannot be unlinked because BiDirectionalOwner requires BiDirectionalDog'); + }); + }, + ); + + specify( + 'Update form for parent of bidirectional 1:m should throw when disconnecting child if child requires parent', + () => { + cy.get('#DataStoreFormUpdateBidirectionalDog').within(() => { + removeArrayItem('Bone'); + + cy.contains('Submit').click(); + cy.contains( + 'cannot be unlinked from BiDirectionalDog because biDirectionalDogBiDirectionalToysId is a required field.', + ); + }); + }, + ); +}); diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/form/BidirectionalOwner-spec.cy.ts b/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/form/BidirectionalOwner-spec.cy.ts new file mode 100644 index 000000000..08993cb26 --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/form/BidirectionalOwner-spec.cy.ts @@ -0,0 +1,65 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import { + clickAddToArray, + getArrayFieldButtonByLabel, + getInputByLabel, + removeArrayItem, + typeInAutocomplete, +} from '../../utils/form'; + +describe('FormTests - DSBidirectionalOwner', () => { + beforeEach(() => { + cy.visit('http://localhost:3000/form-tests/DSBidirectionalOwner'); + }); + specify( + 'create form for the parent of bidirectional 1:1 ' + + 'should throw when connecting to child of another parent ' + + 'if parent requires child', + () => { + cy.get('#DataStoreFormCreateBidirectionalOwner').within(() => { + getInputByLabel('Name').type('New Fluffy Owner'); + getArrayFieldButtonByLabel('Bi directional dog').click(); + typeInAutocomplete('F{downArrow}{enter}'); + clickAddToArray(); + + cy.contains('Submit').click(); + cy.contains( + 'cannot be linked to BiDirectionalOwner because it is already linked to another BiDirectionalOwner', + ); + }); + }, + ); + specify( + 'update form for the parent of bidirectional 1:1 ' + + 'should throw when connecting to child of another parent ' + + 'if parent requires child', + () => { + cy.get('#DataStoreFormUpdateBidirectionalOwner').within(() => { + removeArrayItem('Fluffy'); + getArrayFieldButtonByLabel('Bi directional dog').click(); + typeInAutocomplete('M{downArrow}{enter}'); + clickAddToArray(); + + cy.contains('Submit').click(); + cy.contains( + 'cannot be linked to BiDirectionalOwner because it is already linked to another BiDirectionalOwner', + ); + }); + }, + ); +}); diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/form/BidirectionalToy-spec.cy.ts b/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/form/BidirectionalToy-spec.cy.ts new file mode 100644 index 000000000..98495258f --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/form/BidirectionalToy-spec.cy.ts @@ -0,0 +1,44 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import { clickAddToArray, getArrayFieldButtonByLabel, getInputByLabel, typeInAutocomplete } from '../../utils/form'; + +describe('FormTests - DSBidirectionalToy', () => { + beforeEach(() => { + cy.visit('http://localhost:3000/form-tests/DSBidirectionalToy'); + }); + + specify('Create form for parent of bidirectional 1:1 should update parent of child when connecting to it', () => { + const connectedPhrase1 = 'Toy BiDirectionalDogId is connected to new dog'; + const connectedPhrase2 = 'Toy biDirectionalDogBiDirectionalToysId is connected to new dog'; + + cy.get('#DataStoreFormCreateBidirectionalDog').within(() => { + cy.contains(connectedPhrase1).should('not.exist'); + cy.contains(connectedPhrase2).should('not.exist'); + + getInputByLabel('Name').type('Spot'); + + getArrayFieldButtonByLabel('Bi directional toys').click(); + typeInAutocomplete('B{downArrow}{enter}'); + clickAddToArray(); + + cy.contains('Submit').click(); + + cy.contains(connectedPhrase1); + cy.contains(connectedPhrase2); + }); + }); +}); diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/form/CustomDog-spec.cy.ts b/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/form/CustomDog-spec.cy.ts new file mode 100644 index 000000000..d297a8e72 --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/form/CustomDog-spec.cy.ts @@ -0,0 +1,93 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import { getInputByLabel, typeInAutocomplete } from '../../utils/form'; + +describe('FormTests - CustomDog', () => { + beforeEach(() => { + cy.visit('http://localhost:3000/form-tests/CustomDog'); + }); + + specify('non-data-backed create form should validate, clear, and submit', () => { + const ErrorMessageMap = { + name: 'Name must be longer than 1 character', + age: 'Age must be greater than 0', + validEmail: 'The value must be a valid email address', + customValidation: 'All dog emails are yahoo emails', + ip: 'The value must be an IPv4 or IPv6 address', + }; + cy.get('#CustomFormCreateDog').within(() => { + const blurField = () => cy.contains('Register your dog').click(); + + // should not submit if required field empty + cy.contains('Submit').click(); + cy.contains('submitted: false'); + + // validates email + getInputByLabel('Email').type('fdfdsfd'); + // does not validate onChange if no error + cy.contains(ErrorMessageMap.validEmail).should('not.exist'); + // validates on blur + blurField(); + cy.contains(ErrorMessageMap.validEmail); + // validates onChange if error + getInputByLabel('Email').type('jd@yahoo.com'); + cy.contains(ErrorMessageMap.validEmail).should('not.exist'); + + cy.get('select').select('Green').should('have.value', 'Green'); + + cy.contains('Clear').click(); + + cy.get('select').should('have.value', ''); + + // validates on blur & extends with onValidate prop + getInputByLabel('Name').type('S'); + blurField(); + getInputByLabel('Age').type('-1'); + blurField(); + getInputByLabel('Email').type('spot@gmail.com'); + blurField(); + getInputByLabel('IP Address*').type('invalid ip'); + blurField(); + cy.contains(ErrorMessageMap.name); + cy.contains(ErrorMessageMap.age); + cy.contains(ErrorMessageMap.validEmail).should('not.exist'); + cy.contains(ErrorMessageMap.customValidation); + cy.contains(ErrorMessageMap.ip); + + // clears and submits + cy.contains('Clear').click(); + getInputByLabel('Name').type('Spot'); + blurField(); + getInputByLabel('Age').type('3'); + blurField(); + getInputByLabel('Email').type('spot@yahoo.com'); + blurField(); + getInputByLabel('IP Address*').type('192.0.2.146'); + blurField(); + cy.get('select').select('Blue'); + typeInAutocomplete('Ret{downArrow}{enter}'); + cy.contains('Submit').click(); + cy.contains('submitted: true'); + cy.contains('name: Spot'); + cy.contains('age: 3'); + cy.contains('email: spot@yahoo.com'); + cy.contains('ip: 192.0.2.146'); + cy.contains('color: Blue'); + cy.contains('Retriever'); + }); + }); +}); diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/form/CustomNestedJSON-spec.cy.ts b/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/form/CustomNestedJSON-spec.cy.ts new file mode 100644 index 000000000..589b0ef1b --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/form/CustomNestedJSON-spec.cy.ts @@ -0,0 +1,39 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import { getDecoratedLabelSibling } from '../../utils/form'; + +describe('FormTests - CustomNestedJSON', () => { + beforeEach(() => { + cy.visit('http://localhost:3000/form-tests/CustomNestedJSON'); + }); + + specify('create form created through nested JSON should have a working nested TextField array', () => { + cy.get('#CustomFormCreateNestedJson').within(() => { + cy.contains('Add item').click(); + getDecoratedLabelSibling('Animals - optional').type('String1'); + cy.contains('Add').click(); + cy.contains('Add item').click(); + getDecoratedLabelSibling('Animals - optional').type('String2'); + cy.contains('Add').click(); + cy.contains('String1').should('exist'); + cy.contains('String2').should('exist'); + cy.contains('Clear').click(); + cy.contains('String1').should('not.exist'); + cy.contains('String2').should('not.exist'); + }); + }); +}); diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/form/DSAllSupportedFormFields-spec.cy.ts b/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/form/DSAllSupportedFormFields-spec.cy.ts new file mode 100644 index 000000000..3186e87f1 --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/form/DSAllSupportedFormFields-spec.cy.ts @@ -0,0 +1,491 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import { + getInputByLabel, + getArrayFieldButtonByLabel, + getTextAreaByLabel, + clickAddToArray, + removeArrayItem, + typeInAutocomplete, +} from '../../utils/form'; + +describe('FormTests - DSAllSupportedFormFields', () => { + beforeEach(() => { + cy.visit('http://localhost:3000/form-tests/DSAllSupportedFormFields'); + }); + + specify('data-backed create form should save to DataStore with blank values in non-required fields', () => { + cy.get('#DataStoreFormCreateAllSupportedFormFields').within(() => { + getInputByLabel('String').type('Create1String'); + + cy.contains('Submit').click(); + + cy.contains(/Create1String/).then((recordElement: JQuery) => { + const record = JSON.parse(recordElement.text()); + + expect(record.string).to.equal('Create1String'); + }); + }); + }); + + specify('data-backed create form should save scalar, array, non-model, and relationship fields', () => { + cy.get('#DataStoreFormCreateAllSupportedFormFields').within(() => { + getInputByLabel('String').type('Create1String'); + + // String array + getArrayFieldButtonByLabel('String array').click(); + getInputByLabel('String array').type('String1'); + clickAddToArray(); + + getInputByLabel('Int').type('40'); + getInputByLabel('Float').type('40.2'); + getInputByLabel('Aws date').type('2022-10-12'); + getInputByLabel('Aws time').type('10:12'); + getInputByLabel('Aws date time').type('2017-06-01T08:30'); + getInputByLabel('Aws timestamp').type('1669854600000'); + + getInputByLabel('Aws email').type('myemail@yahoo.com'); + getInputByLabel('Aws url').type('https://amazon.com'); + getInputByLabel('Aws ip address').type('192.0.2.146'); + cy.get('.amplify-switch-track').click(); + getTextAreaByLabel('Aws json').type(JSON.stringify({ myKey: 'myValue' }), { parseSpecialCharSequences: false }); + getTextAreaByLabel('Non model field').type(JSON.stringify({ StringVal: 'myValue' }), { + parseSpecialCharSequences: false, + }); + + getArrayFieldButtonByLabel('Non model field array').click(); + getTextAreaByLabel('Non model field array').type(JSON.stringify({ StringVal: 'index1StringValue' }), { + parseSpecialCharSequences: false, + }); + clickAddToArray(); + + getInputByLabel('Aws phone').type('714-234-4829'); + cy.get('select').select('San francisco'); + + // HasOne Autocomplete + getArrayFieldButtonByLabel('Has one user').click(); + cy.get(`.amplify-autocomplete`).within(() => { + cy.get('input').type(`John Lennon{downArrow}{enter}`); + }); + clickAddToArray(); + + // HasMany Autocomplete + getArrayFieldButtonByLabel('Has many students').click(); + cy.get(`.amplify-autocomplete`).within(() => { + cy.get('input').type(`{downArrow}{enter}`); + }); + clickAddToArray(); + + getArrayFieldButtonByLabel('Has many students').click(); + cy.get(`.amplify-autocomplete`).within(() => { + cy.get('input').type(`Sa{downArrow}{enter}`); + }); + clickAddToArray(); + + // BelongsTo Autocomplete + getArrayFieldButtonByLabel('Belongs to owner').click(); + cy.get(`.amplify-autocomplete`).within(() => { + cy.get('input').type(`John{downArrow}{enter}`); + }); + clickAddToArray(); + + // ManyToMany Autocomplete + getArrayFieldButtonByLabel('Many to many tags').click(); + cy.get(`.amplify-autocomplete`).within(() => { + cy.get('input').type(`Red{downArrow}{enter}`); + }); + clickAddToArray(); + getArrayFieldButtonByLabel('Many to many tags').click(); + cy.get(`.amplify-autocomplete`).within(() => { + cy.get('input').type(`B{downArrow}{enter}`); + }); + clickAddToArray(); + getArrayFieldButtonByLabel('Many to many tags').click(); + cy.get(`.amplify-autocomplete`).within(() => { + cy.get('input').type(`Gr{downArrow}{enter}`); + }); + clickAddToArray(); + + cy.contains('Submit').click(); + + cy.contains(/Create1String/).then((recordElement: JQuery) => { + const record = JSON.parse(recordElement.text()); + + expect(record.string).to.equal('Create1String'); + expect(record.int).to.equal(40); + expect(record.float).to.equal(40.2); + expect(record.awsDate).to.equal('2022-10-12'); + expect(record.awsTime).to.equal('10:12'); + expect(record.awsDateTime).to.equal('2017-06-01T08:30:00.000Z'); + expect(record.awsTimestamp).to.equal(1669854600000); + expect(record.awsEmail).to.equal('myemail@yahoo.com'); + expect(record.awsUrl).to.equal('https://amazon.com'); + expect(record.awsIPAddress).to.equal('192.0.2.146'); + expect(record.awsJson.myKey).to.equal('myValue'); + expect(record.nonModelField.StringVal).to.equal('myValue'); + expect(record.nonModelFieldArray[0].StringVal).to.equal('index1StringValue'); + expect(record.awsPhone).to.equal('714-234-4829'); + expect(record.enum).to.equal('SAN_FRANCISCO'); + expect(record.stringArray[0]).to.equal('String1'); + expect(record.HasOneUser.firstName).to.equal('John'); + expect(record.HasManyStudents.length).to.equal(2); + expect(record.HasManyStudents?.[0].name).to.equal('David'); + expect(record.HasManyStudents?.[0].allSupportedFormFieldsID).to.equal(record.id); + expect(record.HasManyStudents?.[1].name).to.equal('Sarah'); + expect(record.HasManyStudents?.[1].allSupportedFormFieldsID).to.equal(record.id); + expect(record.BelongsToOwner.name).to.equal('John'); + expect(record.ManyToManyTags[0].label).to.equal('Blue'); + expect(record.ManyToManyTags[1].label).to.equal('Green'); + expect(record.ManyToManyTags[2].label).to.equal('Red'); + }); + }); + }); + + specify( + 'data-backed update form should display current values ' + + 'and update them for scalar, array, non-model, and relationship fields', + () => { + cy.get('#DataStoreFormUpdateAllSupportedFormFields').within(() => { + // label should exist even if no input field displayed + cy.contains('Has one user').should('exist'); + // should be able to hit edit and save + // cypress does not populate value in ci w/out wait + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(8000); + cy.contains('John Lennon').click(); + cy.contains('Save').click(); + cy.contains('John Lennon').should('exist'); + + // should be able to change value + removeArrayItem('John Lennon'); + getArrayFieldButtonByLabel('Has one user').click(); + typeInAutocomplete(`Paul McCartney{downArrow}{enter}`); + clickAddToArray(); + + // Belongs to update + removeArrayItem('John'); + getArrayFieldButtonByLabel('Belongs to owner').click(); + typeInAutocomplete(`George{downArrow}{enter}`); + clickAddToArray(); + + // HasMany + removeArrayItem('Jessica'); + + getArrayFieldButtonByLabel('Has many students').click(); + typeInAutocomplete(`Ma{downArrow}{enter}`); + clickAddToArray(); + + getArrayFieldButtonByLabel('Has many students').click(); + typeInAutocomplete(`Sar{downArrow}{enter}`); + clickAddToArray(); + + // Many to many update + removeArrayItem('Red'); + removeArrayItem('Blue'); + + getArrayFieldButtonByLabel('Many to many tags').click(); + typeInAutocomplete(`Or{downArrow}{enter}`); + clickAddToArray(); + + getArrayFieldButtonByLabel('Many to many tags').click(); + typeInAutocomplete(`Gr{downArrow}{enter}`); + clickAddToArray(); + + const stringField = getInputByLabel('String'); + stringField.should('have.value', 'Update1String'); + stringField.type('X'); + stringField.should('have.value', 'Update1StringX'); + + cy.contains('String1').should('exist'); + getArrayFieldButtonByLabel('String array').click(); + getInputByLabel('String array').type('String2'); + clickAddToArray(); + cy.contains('String2').siblings().should('have.length', 1); + + const intField = getInputByLabel('Int'); + intField.should('have.value', 10); + intField.type('123'); + intField.should('have.value', 10123); + + const floatField = getInputByLabel('Float'); + floatField.should('have.value', 4.3); + floatField.type('456'); + floatField.should('have.value', 4.3456); + + const awsDateField = getInputByLabel('Aws date'); + awsDateField.should('have.value', '2022-11-22'); + awsDateField.type('2023-02-13'); + awsDateField.should('have.value', '2023-02-13'); + + const awsTimeField = getInputByLabel('Aws time'); + awsTimeField.should('have.value', '10:20:30.111'); + awsTimeField.type('12:34:56.789'); + awsTimeField.should('have.value', '12:34:56.789'); + + const awsDateTimeField = getInputByLabel('Aws date time'); + awsDateTimeField.should('have.value', '2022-11-22T10:20'); + awsDateTimeField.type('2023-01-13T11:11'); + awsDateTimeField.should('have.value', '2023-01-13T11:11'); + + const awsTimestampField = getInputByLabel('Aws timestamp'); + awsTimestampField.should('have.value', 100000000); + awsTimestampField.type('1'); + awsTimestampField.should('have.value', 1000000001); + + const awsEmailField = getInputByLabel('Aws email'); + awsEmailField.should('have.value', 'myemail@amazon.com'); + awsEmailField.type('{backspace}{backspace}{backspace}org'); + awsEmailField.should('have.value', 'myemail@amazon.org'); + + const awsUrlField = getInputByLabel('Aws url'); + awsUrlField.should('have.value', 'https://www.amazon.com'); + awsUrlField.type('{selectall}{backspace}https://www.google.com'); + awsUrlField.should('have.value', 'https://www.google.com'); + + const awsIPAddressField = getInputByLabel('Aws ip address'); + awsIPAddressField.should('have.value', '123.12.34.56'); + awsIPAddressField.type('{backspace}{backspace}78'); + awsIPAddressField.should('have.value', '123.12.34.78'); + + // Boolean + cy.contains('Boolean').children('[data-checked="true"]').should('exist'); + cy.contains('Boolean').click(); + cy.contains('Boolean').children('[data-checked="false"]').should('exist'); + + const awsJsonField = getTextAreaByLabel('Aws json'); + awsJsonField.should('have.value', JSON.stringify({ myKey: 'myValue' })); + awsJsonField.type('{backspace},"secondKey":"secondValue"}'); + awsJsonField.should('have.value', JSON.stringify({ myKey: 'myValue', secondKey: 'secondValue' })); + + const awsPhoneField = getInputByLabel('Aws phone'); + awsPhoneField.should('have.value', '713 343 5938'); + awsPhoneField.type('{backspace}{backspace}{backspace}{backspace}5678'); + awsPhoneField.should('have.value', '713 343 5678'); + + // Enum + cy.get('select').should('have.value', 'NEW_YORK'); + cy.get('select').select('Austin'); + cy.get('select').should('have.value', 'AUSTIN'); + + const nonModelField = getTextAreaByLabel('Non model field'); + nonModelField.should('have.value', JSON.stringify({ StringVal: 'myValue' })); + nonModelField.type('{backspace},"BoolVal":true}'); + nonModelField.should('have.value', JSON.stringify({ StringVal: 'myValue', BoolVal: true })); + + cy.contains(JSON.stringify({ NumVal: 123 })).click(); + const nonModelFieldArray = getTextAreaByLabel('Non model field array'); + nonModelFieldArray.should('have.value', JSON.stringify({ NumVal: 123 })); + nonModelFieldArray.type('{moveToEnd}{backspace}{backspace}{backspace}{backspace}456}'); + cy.contains('Save').click(); + cy.contains(JSON.stringify({ NumVal: 456 })).should('exist'); + + getArrayFieldButtonByLabel('Non model field array').click(); + getTextAreaByLabel('Non model field array').type(JSON.stringify({ StringVal: 'index1StringValue' }), { + parseSpecialCharSequences: false, + }); + clickAddToArray(); + + cy.contains('Submit').click(); + + cy.contains(/Update1String/).then((recordElement: JQuery) => { + const record = JSON.parse(recordElement.text()); + + expect(record.string).to.equal('Update1StringX'); + expect(record.stringArray).to.deep.equal(['String1', 'String2']); + expect(record.int).to.equal(10123); + expect(record.float).to.equal(4.3456); + expect(record.awsDate).to.equal('2023-02-13'); + expect(record.awsTime).to.equal('12:34:56.789'); + expect(record.awsDateTime).to.equal('2023-01-13T11:11:00.000Z'); + expect(record.awsTimestamp).to.equal(1000000001); + expect(record.awsEmail).to.equal('myemail@amazon.org'); + expect(record.awsUrl).to.equal('https://www.google.com'); + expect(record.awsIPAddress).to.equal('123.12.34.78'); + expect(record.boolean).to.equal(false); + expect(record.awsJson).to.deep.equal({ myKey: 'myValue', secondKey: 'secondValue' }); + expect(record.awsPhone).to.equal('713 343 5678'); + expect(record.enum).to.equal('AUSTIN'); + expect(record.nonModelField).to.deep.equal({ StringVal: 'myValue', BoolVal: true }); + expect(record.nonModelFieldArray[0].NumVal).to.equal(456); + expect(record.nonModelFieldArray[1].StringVal).to.equal('index1StringValue'); + expect(record.HasOneUser.firstName).to.equal('Paul'); + expect(record.ManyToManyTags[0].label).to.equal('Green'); + expect(record.ManyToManyTags[1].label).to.equal('Orange'); + expect(record.BelongsToOwner.name).to.equal('George'); + expect(record.HasManyStudents.length).to.equal(3); + expect(record.HasManyStudents[0].name).to.equal('David'); + expect(record.HasManyStudents[0].allSupportedFormFieldsID).to.equal(record.id); + expect(record.HasManyStudents[1].name).to.equal('Sarah'); + expect(record.HasManyStudents[1].allSupportedFormFieldsID).to.equal(record.id); + expect(record.HasManyStudents[2].name).to.equal('Matthew'); + expect(record.HasManyStudents[2].allSupportedFormFieldsID).to.equal(record.id); + }); + }); + }, + ); + + specify( + 'data-backed update form should be able to empty out scalar, array, non-model, and relationship fields', + () => { + cy.get('#DataStoreFormUpdateAllSupportedFormFields').within(() => { + const hasOneUserItem = 'John Lennon'; + cy.contains(hasOneUserItem).should('exist'); + removeArrayItem(hasOneUserItem); + cy.contains(hasOneUserItem).should('not.exist'); + + const belongsToOwnerItem = 'John -'; + cy.contains(belongsToOwnerItem).should('exist'); + removeArrayItem(belongsToOwnerItem); + cy.contains(belongsToOwnerItem).should('not.exist'); + + const hasManyStudentsItems = ['David -', 'Jessica -']; + cy.contains(hasManyStudentsItems[0]).should('exist'); + cy.contains(hasManyStudentsItems[1]).should('exist'); + removeArrayItem(hasManyStudentsItems[0]); + removeArrayItem(hasManyStudentsItems[1]); + cy.contains(hasManyStudentsItems[0]).should('not.exist'); + cy.contains(hasManyStudentsItems[1]).should('not.exist'); + + const manyToManyTagsItems = ['Red -', 'Blue -']; + cy.contains(manyToManyTagsItems[0]).should('exist'); + cy.contains(manyToManyTagsItems[1]).should('exist'); + removeArrayItem(manyToManyTagsItems[0]); + removeArrayItem(manyToManyTagsItems[1]); + cy.contains(manyToManyTagsItems[0]).should('not.exist'); + cy.contains(manyToManyTagsItems[1]).should('not.exist'); + + const stringArrayItem = 'String1'; + cy.contains(stringArrayItem).should('exist'); + removeArrayItem(stringArrayItem); + cy.contains(stringArrayItem).should('not.exist'); + + const intField = getInputByLabel('Int'); + intField.should('have.value', 10); + intField.clear(); + intField.should('have.value', ''); + + const floatField = getInputByLabel('Float'); + floatField.should('have.value', 4.3); + floatField.clear(); + floatField.should('have.value', ''); + + const awsDateField = getInputByLabel('Aws date'); + awsDateField.should('have.value', '2022-11-22'); + awsDateField.clear(); + awsDateField.should('have.value', ''); + + const awsTimeField = getInputByLabel('Aws time'); + awsTimeField.should('have.value', '10:20:30.111'); + awsTimeField.clear(); + awsTimeField.should('have.value', ''); + + const awsDateTimeField = getInputByLabel('Aws date time'); + awsDateTimeField.should('have.value', '2022-11-22T10:20'); + awsDateTimeField.clear(); + awsDateTimeField.should('have.value', ''); + + const awsTimestampField = getInputByLabel('Aws timestamp'); + awsTimestampField.should('have.value', 100000000); + awsTimestampField.clear(); + awsTimestampField.should('have.value', ''); + + const awsEmailField = getInputByLabel('Aws email'); + awsEmailField.should('have.value', 'myemail@amazon.com'); + awsEmailField.clear(); + awsEmailField.should('have.value', ''); + + const awsUrlField = getInputByLabel('Aws url'); + awsUrlField.should('have.value', 'https://www.amazon.com'); + awsUrlField.clear(); + awsUrlField.should('have.value', ''); + + const awsIPAddressField = getInputByLabel('Aws ip address'); + awsIPAddressField.should('have.value', '123.12.34.56'); + awsIPAddressField.clear(); + awsIPAddressField.should('have.value', ''); + + const awsJsonField = getTextAreaByLabel('Aws json'); + awsJsonField.should('have.value', JSON.stringify({ myKey: 'myValue' })); + awsJsonField.clear(); + awsJsonField.should('have.value', ''); + + const awsPhoneField = getInputByLabel('Aws phone'); + awsPhoneField.should('have.value', '713 343 5938'); + awsPhoneField.clear(); + awsPhoneField.should('have.value', ''); + + // Enum + cy.get('select').should('have.value', 'NEW_YORK'); + cy.get('select').select('Please select an option'); + cy.get('select').should('have.value', ''); + + const nonModelField = getTextAreaByLabel('Non model field'); + nonModelField.should('have.value', JSON.stringify({ StringVal: 'myValue' })); + nonModelField.clear(); + nonModelField.should('have.value', ''); + + const nonModelFieldArrayItem = JSON.stringify({ NumVal: 123 }); + cy.contains(nonModelFieldArrayItem).should('exist'); + removeArrayItem(nonModelFieldArrayItem); + cy.contains(nonModelFieldArrayItem).should('not.exist'); + + cy.contains('Submit').click(); + + cy.contains(/Update1String/).then((recordElement: JQuery) => { + const record = JSON.parse(recordElement.text()); + + expect(record.stringArray).to.deep.equal([]); + expect(record.int).to.equal(null); + expect(record.float).to.equal(null); + expect(record.awsDate).to.equal(null); + expect(record.awsTime).to.equal(null); + expect(record.awsDateTime).to.equal(null); + expect(record.awsTimestamp).to.equal(null); + expect(record.awsEmail).to.equal(null); + expect(record.awsUrl).to.equal(null); + expect(record.awsIPAddress).to.equal(null); + expect(record.awsJson).to.equal(null); + expect(record.awsPhone).to.equal(null); + expect(record.enum).to.equal(null); + expect(record.nonModelField).to.equal(null); + expect(record.nonModelFieldArray).to.deep.equal([]); + expect(record.HasOneUser).to.equal(undefined); + expect(record.BelongsToOwner).to.equal(undefined); + expect(record.HasManyStudents).to.deep.equal([]); + expect(record.ManyToManyTags).to.deep.equal([]); + }); + }); + }, + ); + + specify('scalar relationship labels should be hyphenated like model fields', () => { + cy.get('#DataStoreFormCreateAllSupportedFormFieldsScalar').within(() => { + // cypress does not populate value in ci w/out wait + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(3000); + getArrayFieldButtonByLabel('All supported form fields has one user id').click(); + typeInAutocomplete(`P{downArrow}{enter}`); + clickAddToArray(); + cy.contains('Paul - ').should('exist'); + + getArrayFieldButtonByLabel('All supported form fields belongs to owner id').click(); + typeInAutocomplete(`J{downArrow}{enter}`); + clickAddToArray(); + cy.contains('John - ').should('exist'); + }); + }); +}); diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/form/DSCPKTeacher-spec.cy.ts b/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/form/DSCPKTeacher-spec.cy.ts new file mode 100644 index 000000000..535e53473 --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/form/DSCPKTeacher-spec.cy.ts @@ -0,0 +1,109 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import { + getInputByLabel, + getArrayFieldButtonByLabel, + typeInAutocomplete, + clickAddToArray, + removeArrayItem, +} from '../../utils/form'; + +describe('FormTests - DSCPKTeacher', () => { + beforeEach(() => { + cy.visit('http://localhost:3000/form-tests/DSCPKTeacher'); + }); + + specify( + 'create form for model with CPK and relationships to other models with CPK should create relationships', + () => { + cy.get('#DataStoreFormCreateCPKTeacher').within(() => { + getInputByLabel('Special teacher id').type('Create1ID'); + + // check error message shows on closed ArrayField + cy.contains('Submit').click(); + cy.contains('CPKStudent is required'); + + // hasOne + getArrayFieldButtonByLabel('Cpk student').click(); + typeInAutocomplete('Her{downArrow}{enter}'); + clickAddToArray(); + + // manyToMany + getArrayFieldButtonByLabel('Cpk classes').click(); + typeInAutocomplete('English{downArrow}{enter}'); + clickAddToArray(); + + // hasMany + getArrayFieldButtonByLabel('Cpk projects').click(); + typeInAutocomplete('Either{downArrow}{enter}'); + clickAddToArray(); + + cy.contains('Submit').click(); + + cy.contains(/Create1ID/).then((recordElement: JQuery) => { + const record = JSON.parse(recordElement.text()); + + expect(record.cPKTeacherCPKStudentSpecialStudentId).to.equal('Hermione'); + expect(record.CPKStudent.specialStudentId).to.equal('Hermione'); + expect(record.CPKClasses.length).to.equal(1); + expect(record.CPKClasses[0].specialClassId).to.equal('English'); + expect(record.CPKProjects.length).to.equal(1); + expect(record.CPKProjects[0].specialProjectId).to.equal('Either/Or'); + }); + }); + }, + ); + + specify( + 'update form for model with CPK and relationships ' + + 'to other models with CPK should display current values and update relationships', + () => { + cy.get('#DataStoreFormUpdateCPKTeacher').within(() => { + // hasOne + removeArrayItem('Harry'); + getArrayFieldButtonByLabel('Cpk student').click(); + typeInAutocomplete('Her{downArrow}{enter}'); + clickAddToArray(); + + // manyToMany + removeArrayItem('Math'); + getArrayFieldButtonByLabel('Cpk classes').click(); + typeInAutocomplete('English{downArrow}{enter}'); + clickAddToArray(); + + // hasMany + removeArrayItem('Figure 8'); + getArrayFieldButtonByLabel('Cpk projects').click(); + typeInAutocomplete('Either{downArrow}{enter}'); + clickAddToArray(); + + cy.contains('Submit').click(); + + cy.contains(/Update1ID/).then((recordElement: JQuery) => { + const record = JSON.parse(recordElement.text()); + + expect(record.cPKTeacherCPKStudentSpecialStudentId).to.equal('Hermione'); + expect(record.CPKStudent.specialStudentId).to.equal('Hermione'); + expect(record.CPKClasses.length).to.equal(1); + expect(record.CPKClasses[0].specialClassId).to.equal('English'); + expect(record.CPKProjects.length).to.equal(1); + expect(record.CPKProjects[0].specialProjectId).to.equal('Either/Or'); + }); + }); + }, + ); +}); diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/form/DSCar-spec.cy.ts b/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/form/DSCar-spec.cy.ts new file mode 100644 index 000000000..06c43068a --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/form/DSCar-spec.cy.ts @@ -0,0 +1,46 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import { getArrayFieldButtonByLabel, typeInAutocomplete, clickAddToArray, removeArrayItem } from '../../utils/form'; + +describe('FormTests - DSCar', () => { + beforeEach(() => { + cy.visit('http://localhost:3000/form-tests/DSCar'); + }); + + specify( + 'update form for child in bidirectional 1:1 ' + + 'with `fields` specified on parent should display current values ' + + 'and save bidirectionally', + () => { + cy.get('#DataStoreFormUpdateCar').within(() => { + removeArrayItem(/Oceans Fullerton/); + getArrayFieldButtonByLabel('Dealership').click(); + typeInAutocomplete('Tustin Toyota{downArrow}{enter}'); + clickAddToArray(); + cy.contains('Submit').click(); + + cy.contains('.results', /Tustin Toyota/).then((recordElement: JQuery) => { + const record = JSON.parse(recordElement.text()); + + expect(record.dealership.name).to.equal('Tustin Toyota'); + expect(record.newDealershipHasCar).to.equal(true); + expect(record.prevDealershipDoesNotHaveCar).to.equal(true); + }); + }); + }, + ); +}); diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/form/DSCompositeDog-spec.cy.ts b/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/form/DSCompositeDog-spec.cy.ts new file mode 100644 index 000000000..2879e65ae --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/form/DSCompositeDog-spec.cy.ts @@ -0,0 +1,156 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import { + getInputByLabel, + getArrayFieldButtonByLabel, + typeInAutocomplete, + clickAddToArray, + removeArrayItem, +} from '../../utils/form'; + +describe('FormTests - DSCompositeDog', () => { + beforeEach(() => { + cy.visit('http://localhost:3000/form-tests/DSCompositeDog'); + }); + + specify( + 'create form for model with composite keys ' + + 'and relationships with other models with composite keys ' + + 'should save relationships', + () => { + cy.get('#DataStoreFormCreateCompositeDog').within(() => { + getInputByLabel('Name').type('Cookie'); + getInputByLabel('Description').type('mogwai'); + + // hasOne + getArrayFieldButtonByLabel('Composite bowl').click(); + typeInAutocomplete('round-xl{downArrow}{enter}'); + clickAddToArray(); + + // belongsTo + getArrayFieldButtonByLabel('Composite owner').click(); + typeInAutocomplete('Cooper-Gordon{downArrow}{enter}'); + clickAddToArray(); + + // manyToMany + getArrayFieldButtonByLabel('Composite toys').click(); + typeInAutocomplete('chew-red{downArrow}{enter}'); + clickAddToArray(); + + // hasMany + getArrayFieldButtonByLabel('Composite vets').click(); + typeInAutocomplete('Dentistry-Los Angeles{downArrow}{enter}'); + clickAddToArray(); + + cy.contains('Submit').click(); + + cy.contains(/Cookie/).then((recordElement: JQuery) => { + const record = JSON.parse(recordElement.text()); + + expect(record.CompositeBowl.size).to.equal('xl'); + expect(record.CompositeOwner.firstName).to.equal('Gordon'); + expect(record.CompositeToys.length).to.equal(1); + expect(record.CompositeToys[0].color).to.equal('red'); + expect(record.CompositeVets.length).to.equal(1); + expect(record.CompositeVets[0].city).to.equal('Los Angeles'); + }); + }); + }, + ); + + specify( + 'update form for model with composite keys and ' + + 'relationships with other models with composite keys ' + + 'should display current values and update relationships', + () => { + cy.get('#DataStoreFormUpdateCompositeDog').within(() => { + // composite keys should be readonly + getInputByLabel('Name').should('have.attr', 'readonly'); + getInputByLabel('Description').should('have.attr', 'readonly'); + + // hasOne + removeArrayItem('round-xs'); + getArrayFieldButtonByLabel('Composite bowl').click(); + typeInAutocomplete('round-xl{downArrow}{enter}'); + clickAddToArray(); + + // belongsTo + removeArrayItem('Cooper-Dale'); + getArrayFieldButtonByLabel('Composite owner').click(); + typeInAutocomplete('Cooper-Gordon{downArrow}{enter}'); + clickAddToArray(); + + // manyToMany + removeArrayItem('chew-green'); + getArrayFieldButtonByLabel('Composite toys').click(); + typeInAutocomplete('chew-red{downArrow}{enter}'); + clickAddToArray(); + + // hasMany + removeArrayItem('Dentistry-Seattle'); + getArrayFieldButtonByLabel('Composite vets').click(); + typeInAutocomplete('Dentistry-Los Angeles{downArrow}{enter}'); + clickAddToArray(); + + cy.contains('Submit').click(); + + cy.contains(/Yundoo/).then((recordElement: JQuery) => { + const record = JSON.parse(recordElement.text()); + + expect(record.CompositeBowl.size).to.equal('xl'); + expect(record.CompositeOwner.firstName).to.equal('Gordon'); + expect(record.CompositeToys.length).to.equal(1); + expect(record.CompositeToys[0].color).to.equal('red'); + expect(record.CompositeVets.length).to.equal(1); + expect(record.CompositeVets[0].city).to.equal('Los Angeles'); + }); + }); + }, + ); + + specify('update form for model with composite keys should update relationships through the index fields', () => { + cy.get('#DataStoreFormUpdateCompositeDogScalar').within(() => { + // hasOne + removeArrayItem('xs'); + getArrayFieldButtonByLabel('Composite dog composite bowl size').click(); + typeInAutocomplete('xl{downArrow}{enter}'); + clickAddToArray(); + + // belongsTo + removeArrayItem('Dale'); + getArrayFieldButtonByLabel('Composite dog composite owner first name').click(); + typeInAutocomplete('Gordon{downArrow}{enter}'); + clickAddToArray(); + + cy.contains('Submit').click(); + + cy.contains(/Yundoo/).then((recordElement: JQuery) => { + const record = JSON.parse(recordElement.text()); + + expect(record.CompositeBowl.size).to.equal('xl'); + expect(record.CompositeOwner.firstName).to.equal('Gordon'); + }); + }); + }); + + specify('update form for model with composite key should load the record to update when user passes in keys', () => { + cy.get('#DataStoreFormUpdateCompositeDogById').within(() => { + getInputByLabel('Name').should('have.value', 'Yundoo'); + getInputByLabel('Description').should('have.value', 'tiny but mighty'); + }); + }); +}); diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/form/DSCompositeToy-spec.cy.ts b/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/form/DSCompositeToy-spec.cy.ts new file mode 100644 index 000000000..df9492e39 --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/form/DSCompositeToy-spec.cy.ts @@ -0,0 +1,47 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import { getArrayFieldButtonByLabel, typeInAutocomplete, clickAddToArray } from '../../utils/form'; + +describe('FormTests - DSCompositeToy', () => { + beforeEach(() => { + cy.visit('http://localhost:3000/form-tests/DSCompositeToy'); + }); + + specify('update form for child of 1:m relationship should update the secondary indices linking it to parent', () => { + cy.get('#DataStoreFormUpdateCompositeToy').within(() => { + // cypress does not populate value in ci w/out wait + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(3000); + getArrayFieldButtonByLabel('Composite dog composite toys name').click(); + typeInAutocomplete('Yundoo{downArrow}{enter}'); + clickAddToArray(); + + getArrayFieldButtonByLabel('Composite dog composite toys description').click(); + typeInAutocomplete('tiny but mighty{downArrow}{enter}'); + clickAddToArray(); + + cy.contains('Submit').click(); + + cy.contains(/chew/).then((recordElement: JQuery) => { + const record = JSON.parse(recordElement.text()); + + expect(record.compositeDogCompositeToysName).to.equal('Yundoo'); + expect(record.compositeDogCompositeToysDescription).to.equal('tiny but mighty'); + }); + }); + }); +}); diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/form/DSDealership-spec.cy.ts b/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/form/DSDealership-spec.cy.ts new file mode 100644 index 000000000..19a7f8677 --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/form/DSDealership-spec.cy.ts @@ -0,0 +1,46 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import { getArrayFieldButtonByLabel, typeInAutocomplete, clickAddToArray, removeArrayItem } from '../../utils/form'; + +describe('FormTests - DSDealership', () => { + beforeEach(() => { + cy.visit('http://localhost:3000/form-tests/DSDealership'); + }); + + specify( + 'update form for parent of bidirectional 1:m with `fields` ' + + 'defined on the relationship with child should display current values ' + + 'and save bidirectionally', + () => { + cy.get('#DataStoreFormUpdateDealership').within(() => { + removeArrayItem(/Honda/); + getArrayFieldButtonByLabel('Cars').click(); + typeInAutocomplete('Toyota{downArrow}{enter}'); + clickAddToArray(); + cy.contains('Submit').click(); + + cy.contains('.results', /Toyota/).then((recordElement: JQuery) => { + const record = JSON.parse(recordElement.text()); + + expect(record.cars[0].name).to.equal('Toyota'); + expect(record.newCarBelongsToDealership).to.equal(true); + expect(record.newCarBelongsToDealership).to.equal(true); + }); + }); + }, + ); +}); diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/form/ModelWithVariableCollisions-spec.cy.ts b/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/form/ModelWithVariableCollisions-spec.cy.ts new file mode 100644 index 000000000..0438ef1a4 --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/form/ModelWithVariableCollisions-spec.cy.ts @@ -0,0 +1,44 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import { getInputByLabel } from '../../utils/form'; + +describe('FormTests - DSModelWithVariableCollisions', () => { + beforeEach(() => { + cy.visit('http://localhost:3000/form-tests/DSModelWithVariableCollisions'); + }); + + specify( + 'update form for model with field whose name ' + + 'is lower-case-first version of model name should ' + + 'render existing record values', + () => { + cy.get('#DataStoreFormUpdateModelWithVariableCollisions').within(() => { + const originalValue = 'test'; + const updatedValue = 'test2'; + + getInputByLabel('Model with variable collisions').should('have.value', originalValue); + + getInputByLabel('Model with variable collisions').type(updatedValue); + + cy.contains('Submit').click(); + + getInputByLabel('Model with variable collisions').should('have.value', originalValue + updatedValue); + cy.contains(`UpdatedField= ${originalValue}${updatedValue}`); + }); + }, + ); +}); diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/generate-spec.cy.ts b/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/generate-spec.cy.ts new file mode 100644 index 000000000..42a462ceb --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/generate-spec.cy.ts @@ -0,0 +1,246 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +const EXPECTED_SUCCESSFUL_CASES = new Set([ + 'ViewWithButton', + 'ViewTest', + 'CustomButton', + 'ParsedFixedValues', + 'BasicComponentBadge', + 'BasicComponentView', + 'BasicComponentButton', + 'BasicComponentCard', + 'BasicComponentCollection', + 'BasicComponentDivider', + 'BasicComponentFlex', + 'BasicComponentImage', + 'BasicComponentText', + 'BasicComponentCustomRating', + 'CustomFormCreateDog', + 'DataStoreFormCreateAllSupportedFormFields', + 'DataStoreFormCreateAllSupportedFormFieldsScalar', + 'DataStoreFormUpdateAllSupportedFormFields', + 'DataStoreFormUpdateBidirectionalOwner', + 'DataStoreFormUpdateBidirectionalDog', + 'DataStoreFormCreateBidirectionalDog', + 'DataStoreFormCreateBidirectionalOwner', + 'CustomFormCreateNestedJson', + 'DataStoreFormUpdateCPKTeacher', + 'DataStoreFormCreateCPKTeacher', + 'DataStoreFormUpdateCompositeDog', + 'DataStoreFormUpdateCompositeDogScalar', + 'DataStoreFormCreateCompositeDog', + 'DataStoreFormUpdateCompositeToy', + 'DataStoreFormUpdateModelWithVariableCollisions', + 'DataStoreFormUpdateCar', + 'DataStoreFormUpdateDealership', + 'ComponentWithDataBindingWithPredicate', + 'ComponentWithDataBindingWithoutPredicate', + 'ComponentWithSimplePropertyBinding', + 'ComponentWithAuthBinding', + 'CompWithMultipleBindingsWithPred', + 'ComponentWithSlotBinding', + 'BoundDefaultValue', + 'SimplePropertyBindingDefaultValue', + 'SimpleAndBouldDefaultValue', + 'CollectionDefaultValue', + 'ComplexTest1', + 'ComplexTest2', + 'ComplexTest3', + 'ComplexTest4', + 'ComplexTest5', + 'ComplexTest6', + 'ComplexTest7', + 'ComplexTest8', + 'ComplexTest9', + 'ComplexTest10', + 'ComplexTest11', + 'ReneButton', + 'CollectionWithBinding', + 'CollectionWithSort', + 'CollectionWithBindingItemsName', + 'CompositeDogCard', + 'CollectionWithCompositeKeysAndRelationships', + 'CollectionWithBetweenPredicate', + 'PaginatedCollection', + 'SearchableCollection', + 'CustomParent', + 'CustomChildren', + 'CustomParentAndChildren', + 'ComponentWithConcatenation', + 'ComponentWithConditional', + 'ComponentWithBoundPropertyConditional', + 'ComponentWithNestedOverrides', + 'AlertPrimitive', + 'BadgePrimitive', + 'ButtonPrimitive', + 'ButtonGroupPrimitive', + 'CardPrimitive', + 'CheckboxFieldPrimitive', + 'CollectionPrimitive', + 'DividerPrimitive', + 'FlexPrimitive', + 'GridPrimitive', + 'HeadingPrimitive', + 'IconPrimitive', + 'ImagePrimitive', + 'LinkPrimitive', + 'LoaderPrimitive', + 'MenuButtonPrimitive', + 'MenuPrimitive', + 'PaginationPrimitive', + 'PasswordFieldPrimitive', + 'PhoneNumberFieldPrimitive', + 'PlaceholderPrimitive', + 'RadioPrimitive', + 'RadioGroupFieldPrimitive', + 'RatingPrimitive', + 'ScrollViewPrimitive', + 'SearchFieldPrimitive', + 'SelectFieldPrimitive', + 'SliderFieldPrimitive', + 'StepperFieldPrimitive', + 'SwitchFieldPrimitive', + 'TablePrimitive', + 'TextPrimitive', + 'TextAreaFieldPrimitive', + 'TextFieldPrimitive', + 'ToggleButtonPrimitive', + 'ToggleButtonGroupPrimitive', + 'ViewPrimitive', + 'VisuallyHiddenPrimitive', + 'ComponentWithBreakpoint', + 'ComponentWithVariant', + 'ComponentWithVariantAndOverrides', + 'ComponentWithVariantsAndNotOverrideChildProp', + 'MyTheme', + 'ViewWithButton', + 'ViewTest', + 'CustomButton', + 'GoldenBasicComponent', + 'GoldenCollectionWithDataBindingAndPagination', + 'GoldenCollectionWithDataBindingAndSort', + 'GoldenCollectionWithDataBinding', + 'GoldenCollectionWithSearchAndPagination', + 'GoldenCollectionWithSpecificRecord', + 'GoldenComponentWithAuthAttributes', + 'GoldenComponentWithChildrenAndDataBinding', + 'GoldenComponentWithChildren', + 'GoldenComponentWithCustomChildren', + 'GoldenComponentWithConcatAndConditional', + 'GoldenComponentWithConditionalWithoutField', + 'GoldenComponentWithDataBindingAndDatastoreDefault', + 'GoldenComponentWithDataBinding', + 'GoldenComponentWithEvent', + 'GoldenComponentWithExposed', + 'GoldenComponentWithForm', + 'GoldenComponentWithImageWithStorage', + 'GoldenComponentWithTypedProp', + 'GoldenComponentWithVariants', + 'GoldenTheme', + 'Event', + 'SimpleUserCollection', + 'AuthSignOutActions', + 'NavigationActions', + 'InternalMutation', + 'ButtonsToggleState', + 'MutationWithSyntheticProp', + 'SetStateWithoutInitialValue', + 'UpdateVisibility', + 'DataStoreActions', + 'FormWithState', + 'InternalMutation', + 'InputMutationOnClick', + 'ConditionalInMutation', + 'TwoWayBindings', + 'MutationActionBindings', + 'DataStoreActionBindings', + 'CreateModelWithComplexTypes', + 'InitialValueBindings', + 'DataBindingNamedClass', +]); +const EXPECTED_INVALID_INPUT_CASES = new Set([ + 'ComponentMissingProperties', + 'ComponentMissingType', + 'InvalidTheme', + 'CardWithInvalidChildComponentType', +]); +const EXPECTED_INTERNAL_ERROR_CASES = new Set([]); + +const TARGET_GENERATORS = ['ES2016_TSX', 'ES2016_JSX', 'ES5_TSX', 'ES5_JSX']; + +describe('Generate Components', () => { + beforeEach(() => { + cy.visit('http://localhost:3000/generate-tests'); + }); + + it('verifies all tests are categorized', () => { + const missingCases: string[] = []; + cy.get('.generateTest') + .each(($element) => { + const testCase = $element.attr('id').replace('generateTest', ''); + if ( + !EXPECTED_SUCCESSFUL_CASES.has(testCase) && + !EXPECTED_INVALID_INPUT_CASES.has(testCase) && + !EXPECTED_INTERNAL_ERROR_CASES.has(testCase) + ) { + missingCases.push(testCase); + } + }) + .then(() => { + if (missingCases.length > 0) { + throw new Error( + // eslint-disable-next-line max-len + `Expected all tests cases to be mapped, didn't find config for ${missingCases}. Please add them to EXPECTED_INVALID_INPUT_CASES, EXPECTED_INTERNAL_ERROR_CASES, or EXPECTED_SUCCESSFUL_CASES`, + ); + } + }); + }); + + EXPECTED_SUCCESSFUL_CASES.forEach((testCase) => { + it(`Generates a successful result for ${testCase}`, () => { + cy.get(`#generateTest${testCase}`).within(() => { + cy.get('button').click(); + TARGET_GENERATORS.forEach((targetName) => { + cy.get(`.${targetName}`).contains('✅'); + }); + }); + }); + }); + + EXPECTED_INVALID_INPUT_CASES.forEach((testCase) => { + it(`Generates an invalid input result for ${testCase}`, () => { + cy.get(`#generateTest${testCase}`).within(() => { + cy.get('button').click(); + TARGET_GENERATORS.forEach((targetName) => { + cy.get(`.${targetName}`).contains('❌'); + cy.get(`.${targetName}`).contains('InvalidInputError'); + }); + }); + }); + }); + + EXPECTED_INTERNAL_ERROR_CASES.forEach((testCase) => { + it(`Generates an internal error result for ${testCase}`, () => { + cy.get(`#generateTest${testCase}`).within(() => { + cy.get('button').click(); + TARGET_GENERATORS.forEach((targetName) => { + cy.get(`.${targetName}`).contains('❌'); + cy.get(`.${targetName}`).contains('InternalError'); + }); + }); + }); + }); +}); diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/generated-components-spec.cy.ts b/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/generated-components-spec.cy.ts new file mode 100644 index 000000000..75c392587 --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/generated-components-spec.cy.ts @@ -0,0 +1,413 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +describe('Generated Components', () => { + beforeEach(() => { + cy.visit('http://localhost:3000/component-tests'); + }); + + describe('Basic Components', () => { + it('Renders Badge component', () => { + cy.get('#basic-components').contains('Basic Component Badge'); + }); + + it('Renders View component', () => { + cy.get('#basic-components').contains('Basic Component View'); + }); + + it('Renders Button component', () => { + cy.get('#basic-components').contains('Basic Component Button'); + }); + + it('Renders Card component', () => { + cy.get('#basic-components').contains('Basic Component Card'); + }); + + it('Renders Collection component', () => { + cy.get('#basic-components').find('p:contains("Basic Collection Card Text")').should('have.length', 2); + }); + + it('Renders Divider component', () => { + cy.get('#basic-components').find('.amplify-divider'); + }); + + it('Renders Flex component', () => { + cy.get('#basic-components').find('.amplify-flex').contains('Basic Component Flex'); + }); + + it('Renders Image component', () => { + cy.get('#basic-components').find('img'); + }); + + it('Renders Text component', () => { + cy.get('#basic-components').contains('Basic Component Text'); + }); + + it('Renders Custom component', () => { + cy.get('#basic-components').find('.amplify-rating'); + }); + }); + + describe('Generated Components', () => { + describe('Concatenated Data', () => { + it('Renders Button text as a concatenated, bound element', () => { + cy.get('#concat-and-conditional').contains('Harry Callahan'); + }); + + it('Renders Button text as a concatenated, bound element, with overrides', () => { + cy.get('#concat-and-conditional').contains('Norm Gunderson'); + }); + + it('Renders Button text as a concatenated, auth element', () => { + cy.get('#concat-and-conditional').contains('Harry Callahan TestUser'); + }); + }); + + describe('Conditional Data', () => { + it('Renders Button with one background when user is logged in', () => { + cy.get('#conditional1').should('have.css', 'background-color', 'rgb(255, 0, 0)'); + }); + + it('Renders Button with a different background when user is not logged in', () => { + cy.get('#conditional2').should('have.css', 'background-color', 'rgb(0, 0, 255)'); + }); + + it('Renders Button disabled when user is not logged in', () => { + cy.get('#conditional2').get('[disabled]'); + }); + + it('Renders conditional props for simple property binding', () => { + cy.get('#ComponentWithBoundPropertyConditional-no-prop [disabled]').should('not.exist'); + cy.get('#ComponentWithBoundPropertyConditional-true-prop').get('[disabled]'); + cy.get('#ComponentWithBoundPropertyConditional-false-prop [disabled]').should('not.exist'); + }); + }); + }); + + describe('Component Variants', () => { + it('Renders Button disabled when user is not logged in', () => { + cy.get('#variant1').should('have.css', 'font-size', '12px'); + cy.get('#variant2').should('have.css', 'font-size', '40px'); + cy.get('#variant3').should('have.css', 'width', '500px'); + cy.get('#variant4').should('have.css', 'font-size', '15px'); + cy.get('#variant4').contains('ComponentWithVariantWithMappedChildrenProp'); + cy.get('#variant5').contains('ComponentWithVariantWithChildrenProp'); + cy.get('#variant5').should('have.css', 'font-size', '16px'); + cy.get('#variant6').contains('Nice view!! 🏔'); + }); + + it('should have rest props override variant values', () => { + cy.get('#variantWithRest').should('have.css', 'font-size', '10px'); + }); + + it('allows for use of both variants and overrides, prioritizing overrides if they collide', () => { + cy.get('#variantAndOverrideDefault').contains('DefaultText'); + cy.get('#variantAndOverrideVariantValue').contains('Hello'); + cy.get('#variantAndOverrideOverrideApplied').contains('Overriden Text'); + cy.get('#variantAndOverrideVariantValueAndNonOverlappingOverride').contains('Goodbye'); + cy.get('#variantAndOverrideVariantValueAndOverlappingOverride').contains('Overriden Text'); + }); + }); + + describe('Data Binding', () => { + describe('Simple Property Binding', () => { + it('Renders the Bound property', () => { + cy.get('#simplePropIsDisabled').get('[disabled]'); + }); + }); + + describe('DataStore Binding Without Predicate', () => { + it('Renders with and without overrides', () => { + cy.get('#dataStoreBindingWithoutPredicateNoOverride').contains('Al'); + cy.get('#dataStoreBindingWithoutPredicateWithOverride').contains('Override Name'); + }); + }); + + describe('DataStore Binding With Predicate', () => { + it('Renders with and without overrides', () => { + cy.get('#dataStoreBindingWithPredicateNoOverrideNoModel').contains('Buddy'); + cy.get('#dataStoreBindingWithPredicateWithOverride').contains('Override Name'); + }); + + describe('Auth Binding', () => { + it('Renders if user data is not available', () => { + cy.get('#authBinding [alt="User Image"]'); + }); + + it('Renders user data if available', () => { + cy.get('#data-binding').within(() => { + cy.contains('TestUser'); + cy.contains('Mint Chip'); + }); + }); + }); + + describe('Multiple Data Bindings', () => { + it('Renders data from both bound data models', () => { + cy.get('#multipleDataBindings').contains('QA - 2200'); + }); + }); + }); + + describe('Slot Binding', () => { + it('Renders component passed into the slot, overriding nested components', () => { + cy.get('#slotBinding').within(() => { + cy.contains('Customer component'); + cy.contains('Nested child text').should('not.exist'); + }); + }); + }); + }); + + describe('Collections', () => { + it('It renders a list of override values', () => { + cy.get('#collectionWithBindingAndOverrides button').eq(0).contains('Yankee'); + cy.get('#collectionWithBindingAndOverrides button').eq(1).contains('Feather'); + }); + + it('It renders data pulled from local datastore', () => { + cy.get('#collectionWithBindingNoOverrides button').eq(0).contains('Real'); + cy.get('#collectionWithBindingNoOverrides button').eq(1).contains('Another'); + cy.get('#collectionWithBindingNoOverrides button').eq(2).contains('Last'); + }); + + it('It respects sort functionality', () => { + cy.get('#collectionWithSort button').eq(0).contains('LUser1'); + cy.get('#collectionWithSort button').eq(1).contains('LUser2'); + cy.get('#collectionWithSort button').eq(2).contains('LUser3'); + }); + + it('It renders a list of override values with collectionProperty named items', () => { + cy.get('#collectionWithBindingItemsNameWithOverrides button').eq(0).contains('Yankee'); + cy.get('#collectionWithBindingItemsNameWithOverrides button').eq(1).contains('Feather'); + }); + + it('It renders data pulled from local datastore with collectionProperty named items', () => { + cy.get('#collectionWithBindingItemsNameNoOverrides button').eq(0).contains('Real'); + cy.get('#collectionWithBindingItemsNameNoOverrides button').eq(1).contains('Another'); + cy.get('#collectionWithBindingItemsNameNoOverrides button').eq(2).contains('Last'); + }); + + it('It renders paginated collections', () => { + cy.get('#paginatedCollection').contains('Mountain Retreat - $1800'); + cy.get('#paginatedCollection').contains('Beachside Cottage - $1000').should('not.exist'); + cy.get('[aria-label="Go to page 2"]').click(); + cy.get('#paginatedCollection').contains('Beachside Cottage - $1000'); + }); + + it('It renders searchable collections', () => { + cy.get('#searchableCollection').contains('Mountain Retreat - $1800'); + cy.get('#searchableCollection').siblings('.amplify-collection-search').type('Cabin'); + cy.get('#searchableCollection').contains('Mountain Retreat - $1800').should('not.exist'); + cy.get('#searchableCollection').contains('Cabin in the Woods - $600/night'); + }); + + it('Supports overrideItems with context injection', () => { + cy.get('#collectionWithOverrideItems').contains('0 - Doodle, Yankee'); + cy.get('#collectionWithOverrideItems').contains('1 - Cap, Feather'); + }); + + it('Supports overrideItems that return JSX.Element prop values', () => { + cy.get('#collectionWithJSXOverrideItems').within(() => { + cy.contains('Yankee'); + cy.contains('Doodle'); + cy.contains('Feather'); + cy.contains('Cap'); + }); + }); + + it('Supports hasOne, belongsTo, and hasMany relationships', () => { + cy.get('#collectionWithCompositeKeysAndRelationships').within(() => { + cy.contains('Ruca'); + cy.contains('Owner: Erica'); + cy.contains('Bowl: round'); + 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', () => { + it('Renders simple property binding default value', () => { + cy.get('#bound-simple-binding-default') + .invoke('text') + .then((text) => { + expect(text.trim()).equal('Default Binding Property'); + }); + }); + + it('Overrides simple property binding default value', () => { + cy.get('#bound-simple-binding-override') + .invoke('text') + .then((text) => { + expect(text.trim()).equal('Override Simple Binding'); + }); + }); + + it('Renders bound default value', () => { + cy.get('#bound-default') + .invoke('text') + .then((text) => { + expect(text.trim()).equal('Bound Default'); + }); + }); + + it('Overrides bound default value', () => { + cy.get('#bound-override') + .invoke('text') + .then((text) => { + expect(text.trim()).equal('Override Bound'); + }); + }); + + it('Renders simple default value when simple and bound', () => { + cy.get('#simple-and-bound-default') + .invoke('text') + .then((text) => { + expect(text.trim()).equal('Simple Double Default'); + }); + }); + + it('Overrides simple and bound default value', () => { + cy.get('#simple-and-bound-override') + .invoke('text') + .then((text) => { + expect(text.trim()).equal('Override Simple And Bound'); + }); + }); + + it('Renders collection default value', () => { + cy.get('#collection-default') + .find('.amplify-text') + .invoke('text') + .then((text) => { + expect(text.trim()).equal('Collection Default'); + }); + }); + + it('Overrides collection default value', () => { + cy.get('#collection-override') + .find('.amplify-text') + .invoke('text') + .then((text) => { + expect(text.trim()).equal('Override Collection'); + }); + }); + }); + + describe('Parsed Fixed Property Values', () => { + it('String Value', () => { + cy.get('#parsed-fixed-values') + .find('#string-value') + .should('have.attr', 'value') + .should('equal', 'raw string value'); + }); + + it('String Number Value', () => { + cy.get('#parsed-fixed-values').find('#string-number-value').should('have.attr', 'value').should('equal', '67548'); + }); + + it('Parsed Number Value', () => { + cy.get('#parsed-fixed-values').find('#parsed-number-value').get('.amplify-visually-hidden').contains('0.4'); + }); + + it('String Boolean Value', () => { + cy.get('#parsed-fixed-values').find('#string-boolean-value').should('have.attr', 'value').should('equal', 'true'); + }); + + it('Parsed Boolean Value', () => { + cy.get('#parsed-fixed-values').find('#parsed-boolean-value').should('be.disabled'); + }); + + it('String JSON Value', () => { + cy.get('#parsed-fixed-values') + .find('#string-json-value') + .should('have.attr', 'value') + .should('equal', '{"foo": "bar"}'); + }); + + it('Parsed JSON Value', () => { + cy.get('#parsed-fixed-values').find('#parsed-json-value').should('have.attr', 'viewBox', '0 0 24 24'); + }); + + it('String Array Value', () => { + cy.get('#parsed-fixed-values') + .find('#string-array-value') + .should('have.attr', 'value') + .should('equal', '[1,2,3]'); + }); + + it('Parsed Array Value', () => { + cy.get('#parsed-fixed-values').find('#parsed-array-value').contains('123'); + }); + + it('String Null Value', () => { + cy.get('#parsed-fixed-values').find('#string-null-value').should('have.attr', 'value').should('equal', 'null'); + }); + + it('Parsed Null Value', () => { + cy.get('#parsed-fixed-values').find('#parsed-null-value').should('have.text', ''); + }); + }); + + describe('Custom Component', () => { + it('Renders custom children', () => { + cy.get('#custom-component') + .find('#custom-children') + .find('button') + .should('have.attr', 'style', 'color: rgb(255, 0, 0);'); + }); + it('Renders custom parent', () => { + cy.get('#custom-component').find('#custom-parent').should('have.css', 'font-family', '"Times New Roman"'); + }); + + it('Renders custom parent and children', () => { + cy.get('#custom-component') + .find('#custom-parent-and-children') + .should('have.css', 'font-family', '"Times New Roman"') + .find('button') + .should('have.attr', 'style', 'color: rgb(255, 0, 0);'); + }); + }); + + describe('Overrides', () => { + it('renders overrides with the correct indices', () => { + cy.get('#componentWithNestedOverrides').should('have.css', 'background-color', 'rgb(255, 0, 0)'); + cy.get('#componentWithNestedOverrides #ChildFlex3').should('have.css', 'background-color', 'rgb(0, 128, 0)'); + cy.get('#componentWithNestedOverrides #ChildChildFlex1').should('have.css', 'background-color', 'rgb(0, 0, 255)'); + }); + }); + + describe('Generated Themes', () => { + it('Successfully decorates the app', () => { + // amplify-ui theming converts hex color to rgb + cy.get('p.amplify-text').should('have.css', 'color', 'rgb(0, 128, 128)'); + }); + }); + + describe('Reserved Keywords', () => { + it('renders with reseverd keywords props', () => { + cy.get('#reserved-keywords').find('.amplify-text').should('have.text', 'biology'); + }); + }); +}); diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/primitives-spec.cy.ts b/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/primitives-spec.cy.ts new file mode 100644 index 000000000..c36e803a9 --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/primitives-spec.cy.ts @@ -0,0 +1,391 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +describe('Primitives', () => { + beforeEach(() => { + cy.visit('http://localhost:3000/primitives-tests'); + }); + + describe('Alert', () => { + it('Basic', () => { + cy.get('#alert') + .find('.amplify-alert') + .within(() => { + cy.get('div').contains('Alert Text'); + }); + }); + }); + + describe('Badge', () => { + it('Basic', () => { + cy.get('#badge').find('.amplify-badge').contains('Error Found'); + }); + }); + + describe('Button', () => { + it('Basic', () => { + cy.get('#button').within(() => { + cy.get('.amplify-button').should('have.text', 'Hello world!'); + cy.get('.amplify-button').should('have.attr', 'type', 'button'); + cy.get('.amplify-button').should('have.attr', 'data-variation', 'primary'); + }); + }); + }); + + describe('ButtonGroup', () => { + it('Basic', () => { + cy.get('#button-group').within(() => { + cy.get('.amplify-button').should('have.attr', 'data-variation', 'primary'); + }); + }); + }); + + describe('Card', () => { + it('Basic', () => { + cy.get('#card').within(() => { + cy.get('.amplify-card').find('div').should('have.attr', 'style', 'padding: 1rem;'); + }); + }); + }); + + describe('CheckboxField', () => { + it('Basic', () => { + cy.get('#checkbox-field').within(() => { + cy.get('.amplify-checkboxfield').get('.amplify-checkbox__label').should('have.text', 'Subscribe'); + }); + }); + }); + + describe('Collection', () => { + it('Basic', () => { + cy.get('#collection').find('.amplify-card').eq(0).should('have.text', 'Cozy BungalowLorem ipsum dolor sit amet'); + }); + }); + + describe('Divider', () => { + it('Basic', () => { + cy.get('#divider').find('.amplify-divider'); + }); + }); + + describe('Flex', () => { + it('Basic', () => { + cy.get('#flex').within(() => { + cy.get('.amplify-text').eq(0).should('have.text', 'Hello'); + cy.get('.amplify-text').eq(1).should('have.text', 'world'); + }); + }); + }); + + describe('Grid', () => { + it('Basic', () => { + cy.get('#grid') + .get('.amplify-grid') + .within(() => { + cy.get('div').eq(0).should('have.attr', 'style', 'background-color: var(--amplify-colors-blue-10);'); + cy.get('div').eq(1).should('have.attr', 'style', 'background-color: var(--amplify-colors-blue-20);'); + cy.get('div').eq(2).should('have.attr', 'style', 'background-color: var(--amplify-colors-blue-40);'); + cy.get('div').eq(3).should('have.attr', 'style', 'background-color: var(--amplify-colors-blue-60);'); + }); + }); + }); + + describe('Heading', () => { + it('Basic', () => { + cy.get('#heading').find('.amplify-heading').eq(1).should('have.text', 'Hello world!'); + }); + }); + + describe('Icon', () => { + it('Basic', () => { + cy.get('#icon') + .get('.amplify-icon') + .first() + .within(() => { + cy.get('path').should( + 'have.attr', + 'd', + // eslint-disable-next-line max-len + 'M11 7H13V9H11V7ZM11 11H13V17H11V11ZM12 2C6.48 2 2 6.48 2 12C2 17.52 6.48 22 12 22C17.52 22 22 17.52 22 12C22 6.48 17.52 2 12 2ZM12 20C7.59 20 4 16.41 4 12C4 7.59 7.59 4 12 4C16.41 4 20 7.59 20 12C20 16.41 16.41 20 12 20Z', + ); + }); + }); + }); + + describe('Image', () => { + it('Basic', () => { + cy.get('#image').get('.amplify-image').should('have.attr', 'src', '/road-to-milford-new-zealand-800w.jpg'); + }); + }); + + describe('Link', () => { + it('Basic', () => { + cy.get('#link').get('.amplify-link').should('have.text', 'My Link'); + cy.get('#link').get('.amplify-link').should('have.attr', 'href', '/primitives-tests'); + }); + }); + + describe('Loader', () => { + it('Basic', () => { + cy.get('#loader').get('.amplify-loader').should('have.attr', 'data-size', 'large'); + }); + }); + + describe('Menu', () => { + it('Basic', () => { + cy.get('#menu') + .find('.amplify-menu-trigger') + .find('path') + .should('have.attr', 'd', 'M3 18H21V16H3V18ZM3 13H21V11H3V13ZM3 6V8H21V6H3Z'); + + cy.get('.amplify-menu-content__item').should('not.exist'); + cy.get('#menu').find('.amplify-menu-trigger').click({ force: true }); + cy.get('.amplify-menu-content__item').should('have.text', 'Item'); + }); + }); + + describe('MenuButton', () => { + it('Basic', () => { + cy.get('#menu-button').find('.amplify-button').should('have.text', 'Menu Button'); + }); + }); + + describe('Pagination', () => { + it('Basic', () => { + cy.get('#pagination') + .get('.amplify-pagination') + .get('ol') + .within(() => { + cy.get('li').eq(1).should('have.text', 'Page:1'); + cy.get('li').eq(2).should('have.text', '2'); + cy.get('li').eq(6).should('have.text', '…'); + cy.get('li').eq(7).should('have.text', '10'); + }); + }); + }); + + describe('PasswordField', () => { + it('Basic', () => { + cy.get('#password-field').find('.amplify-input').should('have.attr', 'autocomplete', 'current-password'); + }); + }); + + describe('PhoneNumberField', () => { + it('Basic', () => { + cy.get('#phone-number-field').find('.amplify-input').should('have.attr', 'autocomplete', 'tel-national'); + }); + }); + + describe('Placeholder', () => { + it('Basic', () => { + cy.get('#placeholder').find('.amplify-placeholder'); + }); + }); + + describe('Radio', () => { + it('Basic', () => { + cy.get('#radio') + .find('.amplify-radio') + .within(() => { + cy.get('input').should('have.attr', 'value', 'html'); + cy.get('.amplify-text').should('have.text', 'HTML'); + }); + }); + }); + + describe('RadioGroupField', () => { + it('Basic', () => { + cy.get('#radio-group-field') + .find('.amplify-radiogroupfield') + .within(() => { + cy.get('label').should('have.text', 'Languagehtmlcssjavascript'); + cy.get('.amplify-radiogroup').within(() => { + cy.get('.amplify-radio').eq(0).should('have.text', 'html'); + cy.get('.amplify-radio').eq(1).should('have.text', 'css'); + cy.get('.amplify-radio').eq(2).should('have.text', 'javascript'); + }); + }); + }); + }); + + describe('Rating', () => { + it('Basic', () => { + cy.get('#rating').find('.amplify-visually-hidden').should('have.text', '3.7 out of 5 rating'); + }); + }); + + describe('ScrollView', () => { + it('Basic', () => { + cy.get('#scroll-view').find('.amplify-scrollview').should('have.attr', 'style', 'height: 300px; width: 400px;'); + }); + }); + + describe('SearchField', () => { + it('Basic', () => { + cy.get('#search-field').find('.amplify-input').should('have.attr', 'name', 'q'); + }); + }); + + describe('SliderField', () => { + it('Basic', () => { + cy.get('#slider-field') + .find('.amplify-sliderfield') + .within(() => { + cy.get('.amplify-label').contains('50'); + cy.get('.amplify-label').within(() => { + cy.get('span').eq(0).contains('Slider'); + }); + cy.get('.amplify-sliderfield__group') + .find('span') + .find('span') + .eq(0) + .find('span') + .should('have.attr', 'style', 'left: 0%; right: 50%;'); + }); + }); + }); + describe('StepperField', () => { + it('Basic', () => { + cy.get('#stepper-field').within(() => { + cy.get('.amplify-input').should('have.attr', 'type', 'number'); + cy.get('.amplify-input').should('have.attr', 'min', '0'); + cy.get('.amplify-input').should('have.attr', 'max', '10'); + cy.get('.amplify-input').should('have.attr', 'step', '1'); + }); + }); + }); + + describe('SwitchField', () => { + it('Basic', () => { + cy.get('#switch-field') + .find('.amplify-switch__wrapper') + .within(() => { + cy.get('.amplify-switch-label').should('have.text', 'This is a switch'); + cy.get('.amplify-switch-track'); + }); + }); + }); + + describe('Table', () => { + it('Basic', () => { + cy.get('#table') + .find('.amplify-table') + .within(() => { + cy.get('.amplify-table__caption').should('have.text', 'Some fruits'); + cy.get('.amplify-table__head').within(() => { + cy.get('.amplify-table__th').eq(0).should('have.text', 'Citrus'); + cy.get('.amplify-table__th').eq(1).should('have.text', 'Stone Fruit'); + cy.get('.amplify-table__th').eq(2).should('have.text', 'Berry'); + }); + cy.get('.amplify-table__body').within(() => { + cy.get('.amplify-table__row') + .eq(0) + .within(() => { + cy.get('.amplify-table__td').eq(0).should('have.text', 'Orange'); + cy.get('.amplify-table__td').eq(1).should('have.text', 'Nectarine'); + cy.get('.amplify-table__td').eq(2).should('have.text', 'Raspberry'); + }); + cy.get('.amplify-table__row') + .eq(1) + .within(() => { + cy.get('.amplify-table__td').eq(0).should('have.text', 'Grapefruit'); + cy.get('.amplify-table__td').eq(1).should('have.text', 'Apricot'); + cy.get('.amplify-table__td').eq(2).should('have.text', 'Blueberry'); + }); + cy.get('.amplify-table__row') + .eq(2) + .within(() => { + cy.get('.amplify-table__td').eq(0).should('have.text', 'Lime'); + cy.get('.amplify-table__td').eq(1).should('have.text', 'Peach'); + cy.get('.amplify-table__td').eq(2).should('have.text', 'Strawberry'); + }); + }); + cy.get('.amplify-table__foot').within(() => { + cy.get('.amplify-table__th').eq(0).should('have.text', 'Citrus'); + cy.get('.amplify-table__th').eq(1).should('have.text', 'Stone Fruit'); + cy.get('.amplify-table__th').eq(2).should('have.text', 'Berry'); + }); + }); + }); + }); + + describe('Text', () => { + it('Basic', () => { + cy.get('#text').find('.amplify-text').should('have.text', 'Hello world'); + }); + }); + + describe('TextAreaField', () => { + it('Basic', () => { + cy.get('#text-area-field') + .find('.amplify-textareafield') + .within(() => { + cy.get('.amplify-label').contains('Name'); + cy.get('.amplify-text').contains('Please enter valid name'); + cy.get('.amplify-textarea').should('have.attr', 'placeholder', 'Holden'); + }); + }); + }); + + describe('TextField', () => { + it('Basic', () => { + cy.get('#text-field') + .find('.amplify-textfield') + .within(() => { + cy.get('.amplify-label').contains('Name'); + cy.get('.amplify-text').contains('Please enter valid name'); + cy.get('.amplify-input').should('have.attr', 'placeholder', 'Holden'); + }); + }); + }); + + describe('ToggleButton', () => { + it('Basic', () => { + cy.get('#toggle-button').within(() => { + cy.get('.amplify-togglebutton').should('have.text', 'Press me!'); + cy.get('.amplify-togglebutton').should('have.attr', 'aria-pressed', 'false'); + }); + }); + }); + + describe('ToggleButtonGroup', () => { + it('Basic', () => { + cy.get('#toggle-button-group') + .find('.amplify-togglebuttongroup') + .within(() => { + cy.get('.amplify-togglebutton').eq(0).should('have.text', 'bold'); + cy.get('.amplify-togglebutton').eq(0).should('have.attr', 'aria-pressed', 'true'); + cy.get('.amplify-togglebutton').eq(1).should('have.text', 'italic'); + cy.get('.amplify-togglebutton').eq(1).should('have.attr', 'aria-pressed', 'false'); + cy.get('.amplify-togglebutton').eq(2).should('have.text', 'underline'); + cy.get('.amplify-togglebutton').eq(2).should('have.attr', 'aria-pressed', 'false'); + cy.get('.amplify-togglebutton').eq(3).should('have.text', 'color-fill'); + cy.get('.amplify-togglebutton').eq(3).should('have.attr', 'aria-pressed', 'false'); + }); + }); + }); + + describe('View', () => { + it('Basic', () => { + cy.get('#view').find('div').should('have.text', 'Nice view! 🏔'); + }); + }); + + describe('VisuallyHidden', () => { + it('Basic', () => { + cy.get('#visually-hidden').find('button').find('.amplify-visually-hidden').should('have.text', 'Donemark'); + }); + }); +}); diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/snippet-spec.cy.ts b/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/snippet-spec.cy.ts new file mode 100644 index 000000000..6749a29c3 --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/snippet-spec.cy.ts @@ -0,0 +1,22 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +describe('Component Snippet', () => { + describe('Sanity Test', () => { + it('Snippet renders in React app', () => { + cy.visit('http://localhost:3000/snippet-tests'); + }); + }); +}); diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/two-way-binding-spec.cy.ts b/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/two-way-binding-spec.cy.ts new file mode 100644 index 000000000..aa1371584 --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/two-way-binding-spec.cy.ts @@ -0,0 +1,225 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +describe('Two way binding', () => { + beforeEach(() => { + cy.visit('http://localhost:3000/two-way-binding-tests'); + }); + + describe('CheckboxField', () => { + it('updates on ui interaction', () => { + cy.get('#checkbox-field-section').within(() => { + cy.get('.amplify-checkbox__button').invoke('attr', 'data-checked').should('eq', 'false'); + cy.contains('Subscribe').click(); + cy.get('.amplify-checkbox__button').invoke('attr', 'data-checked').should('eq', 'true'); + }); + }); + + it('updates on state mutation', () => { + cy.get('#checkbox-field-section').within(() => { + cy.contains('Subscribe').click(); + cy.get('.amplify-checkbox__button').invoke('attr', 'data-checked').should('eq', 'true'); + cy.contains('Set CheckboxFieldValue').click(); + cy.get('.amplify-checkbox__button').invoke('attr', 'data-checked').should('eq', 'false'); + }); + }); + }); + + describe('PasswordField', () => { + it('updates on ui interaction', () => { + cy.get('#password-field-section').within(() => { + cy.contains('Entered Value').should('not.exist'); + cy.get('input').type('Entered Value'); + cy.contains('Entered Value'); + }); + }); + + it('updates on state mutation', () => { + cy.get('#password-field-section').within(() => { + cy.contains('admin123').should('not.exist'); + cy.contains('Set PasswordFieldValue').click(); + cy.contains('admin123'); + }); + }); + }); + + describe('PhoneNumberField', () => { + it('updates on ui interaction', () => { + cy.get('#phone-number-field-section').within(() => { + cy.contains('Entered Value').should('not.exist'); + cy.get('input').type('Entered Value'); + cy.contains('Entered Value'); + }); + }); + + it('updates on state mutation', () => { + cy.get('#phone-number-field-section').within(() => { + cy.contains('8675309').should('not.exist'); + cy.contains('Set PhoneNumberFieldValue').click(); + cy.contains('8675309'); + }); + }); + }); + + describe('RadioGroupField', () => { + it('updates on ui interaction', () => { + cy.get('#radio-group-field-section').within(() => { + cy.get('#radio-group-field-value').contains('css').should('not.exist'); + cy.contains('css').click(); + cy.get('#radio-group-field-value').contains('css'); + }); + }); + + it('updates on state mutation', () => { + cy.get('#radio-group-field-section').within(() => { + cy.get('#radio-group-field-value').contains('javascript').should('not.exist'); + cy.contains('Set RadioGroupField').click(); + cy.get('#radio-group-field-value').contains('javascript'); + }); + }); + }); + + describe('SearchField', () => { + it('updates on ui interaction', () => { + cy.get('#search-field-section').within(() => { + cy.contains('Entered Value').should('not.exist'); + cy.get('input').type('Entered Value'); + cy.contains('Entered Value'); + }); + }); + + it('updates on state mutation', () => { + cy.get('#search-field-section').within(() => { + cy.contains('UI Docs').should('not.exist'); + cy.contains('Set SearchFieldValue').click(); + cy.contains('UI Docs'); + }); + }); + }); + + describe('SelectField', () => { + it('updates on ui interaction', () => { + cy.get('#select-field-section').within(() => { + cy.contains('banana').should('not.exist'); + cy.get('select').select('banana'); + cy.contains('banana'); + }); + }); + + it('updates on state mutation', () => { + cy.get('#select-field-section').within(() => { + cy.contains('orange').should('not.exist'); + cy.contains('Set SelectFieldValue').click(); + cy.contains('orange'); + }); + }); + }); + + describe('SliderField', () => { + // Disabling ui interaction test, moving thumb slider is failing. + it.skip('updates on ui interaction', () => { + cy.get('#slider-field-section').within(() => { + // cy.contains('90').should('not.exist'); + cy.get('.amplify-sliderfield__thumb').trigger('mousedown'); + cy.get('.amplify-sliderfield__thumb').trigger('mousemove', -20); + cy.get('.amplify-sliderfield__thumb').trigger('mouseup'); + cy.contains('90'); + }); + }); + + it('updates on state mutation', () => { + cy.get('#slider-field-section').within(() => { + cy.contains('90').should('not.exist'); + cy.contains('Set SliderFieldValue').click(); + cy.contains('90'); + }); + }); + }); + + describe('StepperField', () => { + it('updates on ui interaction', () => { + cy.get('#stepper-field-section').within(() => { + cy.contains('1').should('not.exist'); + cy.get('.amplify-stepperfield__button--increase').click(); + cy.contains('1'); + }); + }); + + it('updates on state mutation', () => { + cy.get('#stepper-field-section').within(() => { + cy.contains('9').should('not.exist'); + cy.contains('Set StepperFieldValue').click(); + cy.contains('9'); + }); + }); + }); + + describe('SwitchField', () => { + it('updates on ui interaction', () => { + cy.get('#switch-field-section').within(() => { + cy.get('.amplify-switch-thumb').invoke('attr', 'data-checked').should('eq', 'false'); + cy.contains('Subscribe').click(); + cy.get('.amplify-switch-thumb').invoke('attr', 'data-checked').should('eq', 'true'); + cy.contains('Subscribe').click(); + cy.get('.amplify-switch-thumb').invoke('attr', 'data-checked').should('eq', 'false'); + }); + }); + + it('updates on state mutation', () => { + cy.get('#switch-field-section').within(() => { + cy.get('.amplify-switch-thumb').invoke('attr', 'data-checked').should('eq', 'false'); + cy.contains('Set SwitchFieldValue').click(); + cy.get('.amplify-switch-thumb').invoke('attr', 'data-checked').should('eq', 'true'); + }); + }); + }); + + describe('TextField', () => { + it('updates on ui interaction', () => { + cy.get('#text-field-section').within(() => { + cy.contains('Entered Value').should('not.exist'); + cy.get('input').type('Entered Value'); + cy.contains('Entered Value'); + }); + }); + + it('updates on state mutation', () => { + cy.get('#text-field-section').within(() => { + cy.contains('Hardcoded Value').should('not.exist'); + cy.contains('Set TextFieldValue').click(); + cy.contains('Hardcoded Value'); + }); + }); + }); + + describe('TextAreaField', () => { + it('updates on ui interaction', () => { + cy.get('#text-area-field-section').within(() => { + cy.contains('Entered Value').should('not.exist'); + cy.get('textarea').type('Entered Value'); + cy.contains('Entered Value'); + }); + }); + + it('updates on state mutation', () => { + cy.get('#text-area-field-section').within(() => { + cy.contains('Hardcoded Value').should('not.exist'); + cy.contains('Set TextAreaFieldValue').click(); + cy.contains('Hardcoded Value'); + }); + }); + }); +}); diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/workflow-spec.cy.ts b/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/workflow-spec.cy.ts new file mode 100644 index 000000000..db800d999 --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/e2e/workflow-spec.cy.ts @@ -0,0 +1,263 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +const TEST_ROUTE = 'http://localhost:3000/workflow-tests'; + +describe('Workflow', () => { + beforeEach(() => { + cy.url().then((url) => { + if (url !== TEST_ROUTE) { + cy.visit(TEST_ROUTE); + } + }); + }); + + const defaultText = '✅'; + + describe('Synthetic Events', () => { + it('click', () => { + cy.get('#clicked').should('not.have.text', defaultText); + cy.get('#click').click(); + cy.get('#clicked').should('have.text', defaultText); + }); + + it('doubleclick', () => { + cy.get('#doubleclicked').should('not.have.text', defaultText); + cy.get('#doubleclick').dblclick(); + cy.get('#doubleclicked').should('have.text', defaultText); + }); + + it('mousedown', () => { + cy.get('#mouseddown').should('not.have.text', defaultText); + cy.get('#mousedown').trigger('mousedown'); + cy.get('#mouseddown').should('have.text', defaultText); + }); + + it('mouseenter', () => { + cy.get('#mouseentered').should('not.have.text', defaultText); + cy.get('#mouseenter').click(); // Implicit mouseenter, trigger doesn't seem to work in cypress here + cy.get('#mouseentered').should('have.text', defaultText); + }); + + it('mouseleave', () => { + cy.get('#mouseleft').should('not.have.text', defaultText); + cy.get('#mouseleave').click(); + cy.get('#mouseenter').click(); // Implicit mouseleave, trigger doesn't seem to work in cypress here + cy.get('#mouseleft').should('have.text', defaultText); + }); + + it('mousemove', () => { + cy.get('#mousemoved').should('not.have.text', defaultText); + cy.get('#mousemove').trigger('mousemove'); + cy.get('#mousemoved').should('have.text', defaultText); + }); + + it('mouseout', () => { + cy.get('#mousedout').should('not.have.text', defaultText); + cy.get('#mouseout').trigger('mouseout'); + cy.get('#mousedout').should('have.text', defaultText); + }); + + it('mouseover', () => { + cy.get('#mousedover').should('not.have.text', defaultText); + cy.get('#mouseover').trigger('mouseover'); + cy.get('#mousedover').should('have.text', defaultText); + }); + + it('mouseup', () => { + cy.get('#mousedup').should('not.have.text', defaultText); + cy.get('#mouseup').trigger('mouseup'); + cy.get('#mousedup').should('have.text', defaultText); + }); + + it('change', () => { + cy.get('#changed').should('not.have.text', defaultText); + cy.get('#change').clear().type(defaultText); + cy.get('#changed').should('have.text', defaultText); + }); + + it('input', () => { + cy.get('#inputted').should('not.have.text', defaultText); + cy.get('#input').clear().type(defaultText); + cy.get('#inputted').should('have.text', defaultText); + }); + + it('focus', () => { + cy.get('#focused').should('not.have.text', defaultText); + cy.get('#focus').click(); + cy.get('#focused').should('have.text', defaultText); + }); + + it('blur', () => { + cy.get('#blurred').should('not.have.text', defaultText); + cy.get('#blur').click(); + cy.get('#blurred').should('not.have.text', defaultText); + cy.get('#focus').click(); + cy.get('#blurred').should('have.text', defaultText); + }); + + it('keydown', () => { + cy.get('#keyeddown').should('not.have.text', defaultText); + cy.get('#keydown').type(defaultText); + cy.get('#keyeddown').should('have.text', defaultText); + }); + + it('keypress', () => { + cy.get('#keypressed').should('not.have.text', defaultText); + cy.get('#keypress').type(defaultText); + cy.get('#keypressed').should('have.text', defaultText); + }); + + it('keyup', () => { + cy.get('#keyedup').should('not.have.text', defaultText); + cy.get('#keyup').type(defaultText); + cy.get('#keyedup').should('have.text', defaultText); + }); + }); + + describe('Actions', () => { + describe('Navigation', () => { + it('supports hard navigation events', () => { + cy.visit(TEST_ROUTE); + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(3000); + cy.url().should('include', '/workflow-tests'); + cy.get('#hard-navigate').click(); + cy.url().should('eq', 'https://www.amazon.com/'); + // This shouldn't be necessary given the `before` code, but that's not taking us back as expected + cy.visit(TEST_ROUTE); + }); + + it('supports anchor navigation events', () => { + cy.url().should('include', '/workflow-tests'); + cy.get('#anchor-navigate').click(); + cy.url().should('include', '/workflow-tests#about'); + }); + + it('supports reload navigation events', () => { + cy.get('#i-disappear').click(); + cy.get('#navigation').contains('I Disappear').should('not.exist'); + cy.get('#reload-page').click(); + cy.get('#navigation').contains('I Disappear'); + }); + }); + + describe('Auth', () => { + it('supports local sign out', () => { + cy.get('#auth-state').contains('LoggedOutLocally').should('not.exist'); + cy.get('#sign-out-local').click(); + cy.get('#auth-state').contains('LoggedOutLocally'); + }); + + it('supports global sign out', () => { + cy.get('#auth-state').contains('LoggedOutGlobally').should('not.exist'); + cy.get('#sign-out-global').click(); + cy.get('#auth-state').contains('LoggedOutGlobally'); + }); + }); + + describe('DataStore', () => { + it('supports creating a datastore item, type-casting scalar values', () => { + const expected = 'Din Djarin | age: 200 | isLoggedIn: true'; + cy.get('#user-collection').contains(expected).should('not.exist'); + cy.get('#create-item').click(); + cy.get('#user-collection').contains(expected); + }); + + it('supports updating a datastore item, type-casting scalar values', () => { + const before = 'UpdateMe Me'; + const after = 'Moff Gideon | age: 200 | isLoggedIn: true'; + cy.get('#user-collection').contains(before); + cy.get('#user-collection').contains(after).should('not.exist'); + cy.get('#update-item').click(); + cy.get('#user-collection').contains(before).should('not.exist'); + cy.get('#user-collection').contains(after); + }); + + it('supports deleting a datastore item', () => { + cy.get('#user-collection').contains('DeleteMe Me'); + cy.get('#delete-item').click(); + cy.get('#user-collection').contains('DeleteMe Me').should('not.exist'); + }); + }); + + describe('State & Mutations', () => { + it('supports internal mutations', () => { + cy.get('#color-changing-box').should('have.css', 'background-color', 'rgb(255, 0, 0)'); + cy.get('#update-box-color').click(); + cy.get('#color-changing-box').should('have.css', 'background-color', 'rgb(0, 0, 255)'); + }); + + it('supports controlled components for a form', () => { + cy.get('#user-collection').contains('vizsla123').should('not.exist'); + cy.get('#username-entry').type('123'); + cy.get('#submit-user-form').click(); + cy.get('#user-collection').contains('vizsla123'); + }); + + it('supports synthetic props', () => { + cy.get('#FooBarValue').contains('Baz'); + cy.get('#FooButton').click(); + cy.get('#FooBarValue').contains('Foo'); + }); + + it('supports two components pointing to the same prop', () => { + cy.get('#WithInitialTextDisplay').should('be.visible'); + cy.get('#WithInitialDisplayNoneButton').click(); + cy.get('#WithInitialTextDisplay').should('not.be.visible'); + cy.get('#WithInitialDisplayBlockButton').click(); + cy.get('#WithInitialTextDisplay').should('be.visible'); + }); + + it('supports mutations without an initial value', () => { + cy.get('#NoInitialTextDisplay').should('be.visible'); + cy.get('#NoInitialDisplayNoneButton').click(); + cy.get('#NoInitialTextDisplay').should('not.be.visible'); + 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'); + }); + }); + + it('supports conditional in mutation', () => { + cy.get('#conditional-in-mutation').within(() => { + cy.get('.amplify-text').should('have.text', 'Default Value'); + cy.get('button').click(); + cy.get('.amplify-text').should('have.text', 'Conditional Value'); + }); + }); + }); + + describe('complex models', () => { + it('can create models with nested objects and lists', () => { + cy.get('#complex-model').within(() => { + cy.get('button').click(); + cy.contains('"listElement":["a","b","c","1","2","3"]'); + cy.contains('"myCustomField":{"StringVal":"hi there","NumVal":7,"BoolVal":false}'); + }); + }); + }); + }); +}); diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/scripts/generateCoverageSummary.mjs b/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/scripts/generateCoverageSummary.mjs new file mode 100644 index 000000000..9f27e79ed --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/scripts/generateCoverageSummary.mjs @@ -0,0 +1,63 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import libCoverage from 'istanbul-lib-coverage'; +import libReport from 'istanbul-lib-report'; +import reports from 'istanbul-reports'; + +import coverageJSON from '../../coverage/coverage-final.json' assert { type: 'json' }; + +// TODO: move this to root project as env vars. +const MIN_COVERAGE_THRESHOLDS = { + lines: 70, + statements: 70, + functions: 65, + branches: 50, +}; + +const map = libCoverage.createCoverageMap(); +const summary = libCoverage.createCoverageSummary(); + +const jsonCoverageMap = libCoverage.createCoverageMap(coverageJSON); +map.merge(jsonCoverageMap); + +map.files().forEach((f) => { + const fc = map.fileCoverageFor(f); + const s = fc.toSummary(); + + summary.merge(s); +}); + +const context = libReport.createContext({ + defaultSummarizer: 'nested', + coverageMap: map, +}); +const report = reports.create('text'); + +// call execute to synchronously create and write the report to disk +report.execute(context); + +// print combined summary of integ test coverage +console.log(summary); + +Object.entries(MIN_COVERAGE_THRESHOLDS).forEach(([coverage, threshold]) => { + if (summary[coverage].pct <= threshold) { + console.error( + '\x1b[31m%s\x1b[0m', + `Failed to meet ${coverage.toLocaleUpperCase()} threshold: ${summary[coverage].pct}% < ${threshold}%`, + ); + process.exit(1); + } +}); diff --git a/packages/test-generator/lib/views/index.ts b/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/support/e2e.js similarity index 85% rename from packages/test-generator/lib/views/index.ts rename to packages/test-generator/integration-test-templates-amplify-js-v6/cypress/support/e2e.js index 24cd2264b..b12273c8d 100644 --- a/packages/test-generator/lib/views/index.ts +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/support/e2e.js @@ -13,4 +13,4 @@ See the License for the specific language governing permissions and limitations under the License. */ -export { default as ListingExpanderWithComponentSlot } from './listing-expander-with-component-slot.json'; +import '@cypress/code-coverage/support'; diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/tsconfig.json b/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/tsconfig.json new file mode 100644 index 000000000..07569168d --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/tsconfig.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "types": ["cypress"] + } +} diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/utils/form.ts b/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/utils/form.ts new file mode 100644 index 000000000..e163c26f4 --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/cypress/utils/form.ts @@ -0,0 +1,58 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +export const getInputByLabel = (label) => { + return cy.contains('label', label).parent().find('input'); +}; + +export const getTextAreaByLabel = (label) => { + return cy.contains('label', label).parent().find('textarea'); +}; + +export const getArrayFieldButtonByLabel = (label) => { + return cy.contains(label).next('button'); +}; + +export const getDecoratedLabelSibling = (label) => { + return cy.contains(label).parent().next(); +}; + +export const clickAddToArray = (container?: Cypress.Chainable) => { + if (container) { + container.within(() => { + cy.get('.amplify-button--link').contains('Add').click(); + }); + } else { + cy.get('.amplify-button--link').contains('Add').click(); + } +}; + +export const removeArrayItem = (itemLabel: string) => { + // cypress clicks parent w/out the wait + // open issue: https://github.com/cypress-io/cypress/issues/20527 + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(8000); + cy.contains(itemLabel).within(() => { + cy.get('svg').trigger('mouseover').trigger('click', { force: true }); + }); +}; + +export const typeInAutocomplete = (content: string) => { + cy.get(`.amplify-autocomplete`) + .first() + .within(() => { + cy.get('input').type(content); + }); +}; diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/public/road-to-milford-new-zealand-800w.jpg b/packages/test-generator/integration-test-templates-amplify-js-v6/public/road-to-milford-new-zealand-800w.jpg new file mode 100644 index 0000000000000000000000000000000000000000..fadd23d598f64779ba10e582a067fed9e35dc0f8 GIT binary patch literal 103564 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 n_n_JzVvsxheQd|1i1RgRkaWGvHt+Pzxp++L { + const initializeTestState = async () => { + // DataStore.clear() doesn't appear to reliably work in this scenario. + indexedDB.deleteDatabase('amplify-datastore'); + await DataStore.save(new Listing({ title: 'Default Value' })); + await DataStore.save(new User({ firstName: 'Johnny', lastName: 'Bound Value', age: 45 })); + initializeAuthMockData({ email: 'Auth Value' }); + setInitialized(true); + }; + + initializeTestState(); + }, []); + + // Pulling this binding out here, since currently there's a bug in observeQuery, that doesn't let + // us define a component which gets observable updates if a criteria is defined + // https://github.com/aws-amplify/amplify-js/issues/9573 + const listing = ( + useDataStoreBinding({ + type: 'collection', + model: Listing, + }) as any + ).items[0]; + + if (!isInitialized) { + return null; + } + + return ( + + + + + + ); +} diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/src/App.tsx b/packages/test-generator/integration-test-templates-amplify-js-v6/src/App.tsx new file mode 100644 index 000000000..91251e663 --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/src/App.tsx @@ -0,0 +1,100 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { Amplify } from 'aws-amplify'; +import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; +import ComponentTests from './ComponentTests'; +import GenerateTests from './GenerateTests'; +import PrimitivesTests from './PrimitivesTests'; +import ComplexTests from './ComplexTests'; +import SnippetTests from './SnippetTests'; // eslint-disable-line import/extensions +import WorkflowTests from './WorkflowTests'; +import TwoWayBindingTests from './TwoWayBindingTests'; +import ActionBindingTests from './ActionBindingTests'; +import FormTests from './FormTests'; +import { DATA_STORE_MOCK_EXPORTS, AUTH_MOCK_EXPORTS } from './mock-utils'; + +// use fake endpoint so useDataStoreBinding does not fail +Amplify.configure({ + Auth: { + Cognito: { + ...AUTH_MOCK_EXPORTS, + }, + }, + API: { + GraphQL: { + ...DATA_STORE_MOCK_EXPORTS, + }, + }, +}); + +const HomePage = () => { + return ( + + ); +}; + +export default function App() { + return ( + + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + ); +} diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/src/ComplexTests.tsx b/packages/test-generator/integration-test-templates-amplify-js-v6/src/ComplexTests.tsx new file mode 100644 index 000000000..6b799adb5 --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/src/ComplexTests.tsx @@ -0,0 +1,86 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import '@aws-amplify/ui-react/styles.css'; +import { ThemeProvider, View, Heading, Divider } from '@aws-amplify/ui-react'; +import { + ComplexTest1, + ComplexTest2, + ComplexTest3, + ComplexTest4, + ComplexTest5, + ComplexTest6, + ComplexTest7, + ComplexTest8, + ComplexTest9, + ComplexTest10, + ComplexTest11, +} from './ui-components'; // eslint-disable-line import/extensions + +export default function ComplexTests() { + return ( + + Complex Test 1 + + + + + Complex Test 2 + + + + + Complex Test 3 + + + + + Complex Test 4 + + + + + Complex Test 5 + + + + + Complex Test 6 + + + + Complex Test 7 + + + + Complex Test 8 + + + + Complex Test 9 + + + + Complex Test 10 + + + + Complex Test 11 + + + + + ); +} diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/src/ComponentTests.tsx b/packages/test-generator/integration-test-templates-amplify-js-v6/src/ComponentTests.tsx new file mode 100644 index 000000000..09b9938e5 --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/src/ComponentTests.tsx @@ -0,0 +1,461 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { useEffect, useState, useRef } from 'react'; +import { ThemeProvider } from '@aws-amplify/ui-react'; +import '@aws-amplify/ui-react/styles.css'; +import { DataStore } from '@aws-amplify/datastore'; +import { User, Listing, Class, CompositeBowl, CompositeToy, CompositeOwner, CompositeDog } from './models'; +import { + ViewTest, + ViewWithButton, + CustomButton, + BasicComponentBadge, + BasicComponentView, + BasicComponentButton, + BasicComponentCard, + BasicComponentCollection, + BasicComponentText, + ComponentWithConcatenation, + ComponentWithConditional, + BasicComponentDivider, + BasicComponentFlex, + BasicComponentImage, + BasicComponentCustomRating, + ComponentWithVariant, + ComponentWithVariantAndOverrides, + ComponentWithVariantsAndNotOverrideChildProp, + SimplePropertyBindingDefaultValue, + BoundDefaultValue, + SimpleAndBoundDefaultValue, + CollectionDefaultValue, + MyTheme, + ComponentWithSimplePropertyBinding, + ComponentWithSlotBinding, + ComponentWithDataBindingWithoutPredicate, + ComponentWithDataBindingWithPredicate, + ComponentWithMultipleDataBindingsWithPredicate, + CollectionWithBinding, + CollectionWithSort, + ParsedFixedValues, + CustomChildren, + CustomParent, + CustomParentAndChildren, + CollectionWithBindingItemsName, + ComponentWithBoundPropertyConditional, + ComponentWithNestedOverrides, + PaginatedCollection, + SearchableCollection, + ComponentWithAuthBinding, + DataBindingNamedClass, + CollectionWithCompositeKeysAndRelationships, + CollectionWithBetweenPredicate, +} from './ui-components'; // eslint-disable-line import/extensions +import { initializeAuthMockData } from './mock-utils'; + +const initializeUserTestData = async (): Promise => { + await DataStore.save(new User({ firstName: 'Real', lastName: 'LUser3', age: 29 })); + await DataStore.save(new User({ firstName: 'Another', lastName: 'LUser2', age: 72 })); + await DataStore.save(new User({ firstName: 'Last', lastName: 'LUser1', age: 50 })); + await DataStore.save(new User({ firstName: 'Too Young', lastName: 'LUser0', age: 5 })); +}; + +const initializeListingTestData = async (): Promise => { + await DataStore.save( + new Listing({ title: 'Cozy Bungalow', priceUSD: 1500, description: 'Lorem ipsum dolor sit amet' }), + ); + await DataStore.save( + new Listing({ + title: 'Mountain Retreat', + priceUSD: 1800, + description: 'consectetur adipiscing elit, sed do eiusmod tempor incididunt', + }), + ); + await DataStore.save( + new Listing({ + title: 'Quiet Cottage', + priceUSD: 1100, + description: 'ut labore et dolore magna aliqua. Ut enim ad minim veniam', + }), + ); + await DataStore.save( + new Listing({ + title: 'Creekside Hideaway', + priceUSD: 950, + description: 'quis nostrud exercitation ullamco laboris nisi ut aliquip', + }), + ); + await DataStore.save( + new Listing({ + title: 'Cabin in the Woods', + priceUSD: 600, + description: 'ex ea commodo consequat. Duis aute irure dolor in reprehenderit', + }), + ); + await DataStore.save( + new Listing({ + title: 'Cabin at the Lake (unit 1)', + priceUSD: 700, + description: 'in voluptate velit esse cillum dolore eu fugiat nulla pariatur', + }), + ); + await DataStore.save( + new Listing({ + title: 'Cabin at the Lake (unit 2)', + priceUSD: 800, + description: 'Excepteur sint occaecat cupidatat non proident', + }), + ); + await DataStore.save( + new Listing({ + title: 'Beachside Cottage', + priceUSD: 1000, + description: 'sunt in culpa qui officia deserunt mollit anim id est laborum', + }), + ); + await DataStore.save(new Listing({ title: 'Lush Resort', priceUSD: 3500, description: 'Its real nice' })); + await DataStore.save(new Listing({ title: 'Chalet away from home', priceUSD: 5000, description: 'youll like it' })); +}; + +const initializeCompositeDogTestData = async (): Promise => { + const connectedBowl = await DataStore.save(new CompositeBowl({ shape: 'round', size: 'xl' })); + const connectedOwner = await DataStore.save(new CompositeOwner({ lastName: 'Erica', firstName: 'Raunak' })); + + const connectedToys = await Promise.all([ + DataStore.save(new CompositeToy({ kind: 'stick', color: 'oak' })), + DataStore.save(new CompositeToy({ kind: 'ball', color: 'green' })), + ]); + + const createdRecord = await DataStore.save( + new CompositeDog({ + name: 'Ruca', + description: 'fetch maniac', + CompositeBowl: connectedBowl, + CompositeOwner: connectedOwner, + }), + ); + + await Promise.all( + connectedToys.map((toy) => { + return DataStore.save( + CompositeToy.copyOf(toy, (updated) => { + Object.assign(updated, { + compositeDogCompositeToysName: createdRecord.name, + compositeDogCompositeToysDescription: createdRecord.description, + }); + }), + ); + }), + ); +}; + +export default function ComponentTests() { + const [isInitialized, setInitialized] = useState(false); + const initializeStarted = useRef(false); + + useEffect(() => { + const initializeTestUserData = async () => { + if (initializeStarted.current) { + return; + } + // DataStore.clear() doesn't appear to reliably work in this scenario. + indexedDB.deleteDatabase('amplify-datastore'); + await Promise.all([initializeUserTestData(), initializeListingTestData(), initializeCompositeDogTestData()]); + initializeAuthMockData({ + picture: 'http://media.corporate-ir.net/media_files/IROL/17/176060/Oct18/AWS.png', + username: 'TestUser', + 'custom:favorite_icecream': 'Mint Chip', + }); + setInitialized(true); + }; + + initializeTestUserData(); + initializeStarted.current = true; + }, []); + + if (!isInitialized) { + return null; + } + + return ( + +

Generated Component Tests

+
+

Basic Components

+ + + + + + + + + + +
+ + + +
+

Concatenation and Conditional Tests

+ + + + + + + + +
+
+

Variants

+ + + + + + + + + + + + +
+
+

Data Binding

+ + + + + + + + + Customer component
} /> + +
+

Collections

+ + + + + + + + { + return { + children: `${index} - ${item.lastName}, ${item.firstName}`, + }; + }} + /> + { + return { + children: ( + <> +
{item.lastName}
+
{item.firstName}
+ + ), + }; + }} + /> + { + return { + mySlot: ( +
+ {`Owner: ${item.CompositeOwner?.lastName}`} + {`Bowl: ${item.CompositeBowl?.shape}`} + {`Toys: ${item.CompositeToys?.map((toy: CompositeToy) => toy.kind).join(', ')}`} +
+ ), + }; + }} + /> + +
+
+

Default Value

+ + + + + + + + +
+
+ +
+
+ + + +
+
+

Overrides

+ +
+
+ +
+
+ ); +} diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/src/FormTests/CustomDog.tsx b/packages/test-generator/integration-test-templates-amplify-js-v6/src/FormTests/CustomDog.tsx new file mode 100644 index 000000000..e8d8f01b0 --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/src/FormTests/CustomDog.tsx @@ -0,0 +1,52 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { ThemeProvider, View, Heading, Text } from '@aws-amplify/ui-react'; +import { useState } from 'react'; +import { CustomFormCreateDog } from '../ui-components'; // eslint-disable-line import/extensions, max-len + +export default function () { + const [customFormCreateDogResults, setCustomFormCreateDogResults] = useState({}); + + return ( + + CustomFormCreateDog + + setCustomFormCreateDogResults(r)} + onValidate={{ + email: (value, validationResponse) => { + if (validationResponse.hasError) { + return validationResponse; + } + if (!value?.includes('yahoo.com')) { + return { hasError: true, errorMessage: 'All dog emails are yahoo emails' }; + } + return { hasError: false }; + }, + }} + /> + {`submitted: ${!!Object.keys(customFormCreateDogResults).length}`} + {`name: ${customFormCreateDogResults.name}`} + {`name: ${customFormCreateDogResults.name}`} + {`age: ${customFormCreateDogResults.age}`} + {`email: ${customFormCreateDogResults.email}`} + {`ip: ${customFormCreateDogResults.ip}`} + {`ip: ${customFormCreateDogResults.breed}`} + {`color: ${customFormCreateDogResults.color}`} + + + ); +} diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/src/FormTests/CustomNestedJSON.tsx b/packages/test-generator/integration-test-templates-amplify-js-v6/src/FormTests/CustomNestedJSON.tsx new file mode 100644 index 000000000..aa7f10191 --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/src/FormTests/CustomNestedJSON.tsx @@ -0,0 +1,28 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { ThemeProvider, View, Heading } from '@aws-amplify/ui-react'; +import { CustomFormCreateNestedJson } from '../ui-components'; // eslint-disable-line import/extensions, max-len + +export default function () { + return ( + + CustomFormCreateNestedJson + + undefined} /> + + + ); +} diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/src/FormTests/DSAllSupportedFormFields.tsx b/packages/test-generator/integration-test-templates-amplify-js-v6/src/FormTests/DSAllSupportedFormFields.tsx new file mode 100644 index 000000000..c57e23398 --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/src/FormTests/DSAllSupportedFormFields.tsx @@ -0,0 +1,256 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { ThemeProvider, View, Heading, Text, Divider } from '@aws-amplify/ui-react'; +import React, { useState, useEffect, useRef, SetStateAction } from 'react'; +import { AsyncItem, DataStore } from '@aws-amplify/datastore'; +import { + DataStoreFormCreateAllSupportedFormFields, + DataStoreFormUpdateAllSupportedFormFields, + DataStoreFormCreateAllSupportedFormFieldsScalar, +} from '../ui-components'; // eslint-disable-line import/extensions +import { + Owner, + User, + Tag, + Student, + LazyTag, + AllSupportedFormFields, + AllSupportedFormFieldsTag, + LazyAllSupportedFormFieldsTag, + LazyStudent, +} from '../models'; +import { getModelsFromJoinTableRecords } from '../test-utils'; + +const initializeTestData = async (): Promise => { + await Promise.all([ + DataStore.save(new User({ firstName: 'John', lastName: 'Lennon', age: 29 })), + DataStore.save(new User({ firstName: 'Paul', lastName: 'McCartney', age: 72 })), + DataStore.save(new User({ firstName: 'George', lastName: 'Harrison', age: 50 })), + DataStore.save(new User({ firstName: 'Ringo', lastName: 'Starr', age: 5 })), + DataStore.save(new Owner({ name: 'John' })), + DataStore.save(new Owner({ name: 'Paul' })), + DataStore.save(new Owner({ name: 'George' })), + DataStore.save(new Owner({ name: 'Ringo' })), + DataStore.save(new Student({ name: 'David' })), + DataStore.save(new Student({ name: 'Taylor' })), + DataStore.save(new Student({ name: 'Michael' })), + DataStore.save(new Student({ name: 'Sarah' })), + DataStore.save(new Student({ name: 'Matthew' })), + DataStore.save(new Student({ name: 'Jessica' })), + DataStore.save(new Tag({ label: 'Red' })), + DataStore.save(new Tag({ label: 'Blue' })), + DataStore.save(new Tag({ label: 'Green' })), + DataStore.save(new Tag({ label: 'Orange' })), + ]); +}; + +const initializeUpdate1TestData = async ({ + setDataStoreFormUpdateAllSupportedFormFieldsRecordId, +}: { + setDataStoreFormUpdateAllSupportedFormFieldsRecordId: React.Dispatch>; +}): Promise => { + const connectedUser = (await DataStore.query(User, (u) => u.firstName.eq('John')))[0]; + const connectedOwner = (await DataStore.query(Owner, (owner) => owner.name.eq('John')))[0]; + const connectedTags = await DataStore.query(Tag, (tag) => tag.or((t) => [t.label.eq('Red'), t.label.eq('Blue')])); + const connectedStudents = await DataStore.query(Student, (student) => + student.or((s) => [s.name.eq('David'), s.name.eq('Jessica')]), + ); + const createdRecord = await DataStore.save( + new AllSupportedFormFields({ + string: 'Update1String', + stringArray: ['String1'], + int: 10, + float: 4.3, + awsDate: '2022-11-22', + awsTime: '10:20:30.111', + awsDateTime: '2022-11-22T10:20:30.111Z', + awsTimestamp: 100000000, + awsEmail: 'myemail@amazon.com', + awsUrl: 'https://www.amazon.com', + awsIPAddress: '123.12.34.56', + boolean: true, + awsJson: JSON.stringify({ myKey: 'myValue' }), + nonModelField: { StringVal: 'myValue' }, + nonModelFieldArray: [{ NumVal: 123 }], + awsPhone: '713 343 5938', + enum: 'NEW_YORK', + HasOneUser: connectedUser, + BelongsToOwner: connectedOwner, + }), + ); + + // connect tags through join table + await Promise.all( + connectedTags.reduce((promises: AsyncItem[], tag) => { + promises.push( + DataStore.save( + new AllSupportedFormFieldsTag({ + allSupportedFormFields: createdRecord, + tag, + }), + ), + ); + return promises; + }, []), + ); + + // connect students to form + await Promise.all( + connectedStudents.reduce((promises: AsyncItem[], student) => { + promises.push( + DataStore.save( + Student.copyOf(student, (updated) => { + Object.assign(updated, { allSupportedFormFieldsID: createdRecord.id }); + }), + ), + ); + return promises; + }, []), + ); + + setDataStoreFormUpdateAllSupportedFormFieldsRecordId((prevId) => { + if (!prevId) { + return createdRecord.id; + } + return prevId; + }); +}; + +export default function () { + const [dataStoreFormCreateAllSupportedFormFieldsResults, setDataStoreFormCreateAllSupportedFormFieldsResults] = + useState(''); + const [dataStoreFormUpdateAllSupportedFormFieldsResults, setDataStoreFormUpdateAllSupportedFormFieldsResults] = + useState(''); + const [dataStoreFormUpdateAllSupportedFormFieldsRecordId, setDataStoreFormUpdateAllSupportedFormFieldsRecordId] = + useState(); + const [ + dataStoreFormCreateAllSupportedFormFieldsScalarResults, + setDataStoreFormCreateAllSupportedFormFieldsScalarResults, + ] = useState(''); + const [isInitialized, setInitialized] = useState(false); + + const initializeStarted = useRef(false); + + useEffect(() => { + const initializeTestState = async () => { + if (initializeStarted.current) { + return; + } + // DataStore.clear() doesn't appear to reliably work in this scenario. + indexedDB.deleteDatabase('amplify-datastore'); + await initializeTestData(); + await Promise.all([initializeUpdate1TestData({ setDataStoreFormUpdateAllSupportedFormFieldsRecordId })]); + setInitialized(true); + }; + + initializeTestState(); + initializeStarted.current = true; + }, []); + + if (!isInitialized) { + return null; + } + + return ( + + DataStoreFormCreateAllSupportedFormFields + + { + const records = await DataStore.query(AllSupportedFormFields, (a) => a.string.eq('Create1String')); + const record = records[0]; + + const ManyToManyTags = await getModelsFromJoinTableRecords< + AllSupportedFormFields, + LazyTag, + LazyAllSupportedFormFieldsTag + >(record, 'ManyToManyTags', 'tag'); + ManyToManyTags.sort((a, b) => a.label?.localeCompare(b.label as string) as number); + + setDataStoreFormCreateAllSupportedFormFieldsResults( + JSON.stringify({ + ...record, + HasOneUser: await record.HasOneUser, + BelongsToOwner: await record.BelongsToOwner, + HasManyStudents: await record.HasManyStudents?.toArray(), + ManyToManyTags, + }), + ); + }} + /> + {dataStoreFormCreateAllSupportedFormFieldsResults} + + + DataStoreFormUpdateAllSupportedFormFields + + { + const records = await DataStore.query(AllSupportedFormFields, (a) => a.string.contains('Update1String')); + const record = records[0]; + + const ManyToManyTags = await getModelsFromJoinTableRecords< + AllSupportedFormFields, + LazyTag, + LazyAllSupportedFormFieldsTag + >(record, 'ManyToManyTags', 'tag'); + ManyToManyTags.sort((a, b) => a.label?.localeCompare(b.label as string) as number); + + setDataStoreFormUpdateAllSupportedFormFieldsResults( + JSON.stringify({ + ...record, + HasOneUser: await record.HasOneUser, + BelongsToOwner: await record.BelongsToOwner, + HasManyStudents: await record.HasManyStudents?.toArray(), + ManyToManyTags, + }), + ); + }} + /> + {dataStoreFormUpdateAllSupportedFormFieldsResults} + + + DataStoreFormCreateAllSupportedFormFieldsScalar + + { + const records = await DataStore.query(AllSupportedFormFields, (a) => a.string.eq('Create1String')); + const record = records[0]; + + const ManyToManyTags = await getModelsFromJoinTableRecords< + AllSupportedFormFields, + LazyTag, + LazyAllSupportedFormFieldsTag + >(record, 'ManyToManyTags', 'tag'); + ManyToManyTags.sort((a, b) => a.label?.localeCompare(b.label as string) as number); + + setDataStoreFormCreateAllSupportedFormFieldsScalarResults( + JSON.stringify({ + ...record, + HasOneUser: await record.HasOneUser, + BelongsToOwner: await record.BelongsToOwner, + HasManyStudents: await record.HasManyStudents?.toArray(), + ManyToManyTags, + }), + ); + }} + /> + {dataStoreFormCreateAllSupportedFormFieldsScalarResults} + + + + ); +} diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/src/FormTests/DSBidirectionalDog.tsx b/packages/test-generator/integration-test-templates-amplify-js-v6/src/FormTests/DSBidirectionalDog.tsx new file mode 100644 index 000000000..76829eccd --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/src/FormTests/DSBidirectionalDog.tsx @@ -0,0 +1,112 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import '@aws-amplify/ui-react/styles.css'; +import { ThemeProvider, View, Heading, Text } from '@aws-amplify/ui-react'; +import { useState, useEffect, useRef } from 'react'; +import { DataStore } from '@aws-amplify/datastore'; +import { DataStoreFormUpdateBidirectionalDog } from '../ui-components'; // eslint-disable-line import/extensions, max-len +import { BiDirectionalDog, BiDirectionalOwner, BiDirectionalToy } from '../models'; + +const initializeTestData = async (): Promise<{ + connectedOwner: BiDirectionalOwner; + connectedDog: BiDirectionalDog; +}> => { + const connectedDog = await DataStore.save(new BiDirectionalDog({ name: 'Fluffy' })); + await DataStore.save( + new BiDirectionalToy({ + name: 'Bone', + BiDirectionalDog: connectedDog, + biDirectionalDogID: connectedDog.id, + biDirectionalDogBiDirectionalToysId: connectedDog.id, + }), + ); + const connectedOwner = await DataStore.save( + new BiDirectionalOwner({ + name: 'Fluffys Owner', + BiDirectionalDog: connectedDog, + biDirectionalDogID: connectedDog.id, + }), + ); + await DataStore.save( + BiDirectionalDog.copyOf(connectedDog, (updated) => { + Object.assign(updated, { + BiDirectionalOwner: connectedOwner, + }); + }), + ); + + const connectedDog2 = await DataStore.save(new BiDirectionalDog({ name: 'Max' })); + const connectedOwner2 = await DataStore.save( + new BiDirectionalOwner({ + name: 'Maxs Owner', + BiDirectionalDog: connectedDog2, + biDirectionalDogID: connectedDog2.id, + }), + ); + await DataStore.save( + BiDirectionalDog.copyOf(connectedDog2, (updated) => { + Object.assign(updated, { + BiDirectionalOwner: connectedOwner2, + }); + }), + ); + + return { connectedOwner, connectedDog }; +}; + +export default function () { + const [DataStoreCreateBidirectionalDogRecord, setDataStoreCreateBidirectionalDogRecord] = useState< + BiDirectionalDog | undefined + >(); + const [DataStoreCreateBidirectionalDogError, setDataStoreCreateBidirectionalDogError] = useState(''); + const [isInitialized, setInitialized] = useState(false); + const initializeStarted = useRef(false); + + useEffect(() => { + const initializeTestState = async () => { + if (initializeStarted.current) { + return; + } + // DataStore.clear() doesn't appear to reliably work in this scenario. + indexedDB.deleteDatabase('amplify-datastore'); + const { connectedDog } = await initializeTestData(); + setDataStoreCreateBidirectionalDogRecord(connectedDog); + setInitialized(true); + }; + + initializeTestState(); + initializeStarted.current = true; + }, []); + + if (!isInitialized) { + return null; + } + + return ( + + DataStoreFormUpdateBidirectionalDog + + { + setDataStoreCreateBidirectionalDogError(err); + }} + /> + {DataStoreCreateBidirectionalDogError} + + + ); +} diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/src/FormTests/DSBidirectionalOwner.tsx b/packages/test-generator/integration-test-templates-amplify-js-v6/src/FormTests/DSBidirectionalOwner.tsx new file mode 100644 index 000000000..f562b2354 --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/src/FormTests/DSBidirectionalOwner.tsx @@ -0,0 +1,114 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import '@aws-amplify/ui-react/styles.css'; +import { ThemeProvider, View, Heading, Text } from '@aws-amplify/ui-react'; +import { useState, useEffect, useRef } from 'react'; +import { DataStore } from '@aws-amplify/datastore'; +import { DataStoreFormCreateBidirectionalOwner, DataStoreFormUpdateBidirectionalOwner } from '../ui-components'; // eslint-disable-line import/extensions, max-len +import { BiDirectionalDog, BiDirectionalOwner } from '../models'; + +const initializeTestData = async (): Promise<{ + connectedOwner: BiDirectionalOwner; +}> => { + const connectedDog = await DataStore.save(new BiDirectionalDog({ name: 'Fluffy' })); + const connectedOwner = await DataStore.save( + new BiDirectionalOwner({ + name: 'Fluffys Owner', + BiDirectionalDog: connectedDog, + biDirectionalDogID: connectedDog.id, + }), + ); + await DataStore.save( + BiDirectionalDog.copyOf(connectedDog, (updated) => { + Object.assign(updated, { + BiDirectionalOwner: connectedOwner, + }); + }), + ); + + const connectedDog2 = await DataStore.save(new BiDirectionalDog({ name: 'Max' })); + const connectedOwner2 = await DataStore.save( + new BiDirectionalOwner({ + name: 'Maxs Owner', + BiDirectionalDog: connectedDog2, + biDirectionalDogID: connectedDog2.id, + }), + ); + await DataStore.save( + BiDirectionalDog.copyOf(connectedDog2, (updated) => { + Object.assign(updated, { + BiDirectionalOwner: connectedOwner2, + }); + }), + ); + + return { connectedOwner }; +}; + +export default function () { + const [DataStoreFormUpdateBidirectionalOwnerRecord, setDataStoreFormUpdateBidirectionalOwnerRecord] = useState< + BiDirectionalOwner | undefined + >(); + const [DataStoreFormCreateBidirectionalOwnerError, setDataStoreFormCreateBidirectionalOwnerError] = useState(''); + const [DataStoreFormUpdateBidirectionalOwnerError, setDataStoreFormUpdateBidirectionalOwnerError] = useState(''); + const [isInitialized, setInitialized] = useState(false); + const initializeStarted = useRef(false); + + useEffect(() => { + const initializeTestState = async () => { + if (initializeStarted.current) { + return; + } + // DataStore.clear() doesn't appear to reliably work in this scenario. + indexedDB.deleteDatabase('amplify-datastore'); + const { connectedOwner } = await initializeTestData(); + setDataStoreFormUpdateBidirectionalOwnerRecord(connectedOwner); + + setInitialized(true); + }; + + initializeTestState(); + initializeStarted.current = true; + }, []); + + if (!isInitialized) { + return null; + } + + return ( + + DataStoreFormCreateBidirectionalOwner + + { + setDataStoreFormCreateBidirectionalOwnerError(err); + }} + /> + {DataStoreFormCreateBidirectionalOwnerError} + + DataStoreFormUpdateBidirectionalOwner + + { + setDataStoreFormUpdateBidirectionalOwnerError(err); + }} + /> + {DataStoreFormUpdateBidirectionalOwnerError} + + + ); +} diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/src/FormTests/DSBidirectionalToy.tsx b/packages/test-generator/integration-test-templates-amplify-js-v6/src/FormTests/DSBidirectionalToy.tsx new file mode 100644 index 000000000..37a1c6b3f --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/src/FormTests/DSBidirectionalToy.tsx @@ -0,0 +1,106 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import '@aws-amplify/ui-react/styles.css'; +import { ThemeProvider, View, Heading, Text } from '@aws-amplify/ui-react'; +import { useState, useEffect, useRef } from 'react'; +import { DataStore } from '@aws-amplify/datastore'; +import { DataStoreFormCreateBidirectionalDog } from '../ui-components'; // eslint-disable-line import/extensions +import { BiDirectionalDog, BiDirectionalToy } from '../models'; + +const toyName = 'Bone'; + +const initializeTestData = async (): Promise<{ + connectedDog: BiDirectionalDog; + connectedToy: BiDirectionalToy; +}> => { + const connectedDog = await DataStore.save(new BiDirectionalDog({ name: 'Fluffy' })); + const connectedToy = await DataStore.save( + new BiDirectionalToy({ + name: toyName, + BiDirectionalDog: connectedDog, + biDirectionalDogID: connectedDog.id, + biDirectionalDogBiDirectionalToysId: connectedDog.id, + }), + ); + + return { connectedDog, connectedToy }; +}; + +export default function DSBidirectionalToy() { + const [DogBiDirectionalToyId, setDogBiDirectionalToyId] = useState<{ + toyBiDirectionalDogIdUpdated: string; + biDirectionalDogBiDirectionalToysIdUpdated: string | null | undefined; + }>({ toyBiDirectionalDogIdUpdated: '', biDirectionalDogBiDirectionalToysIdUpdated: '' }); + const [isInitialized, setInitialized] = useState(false); + // setting to null to prevent matching empty string in the test + const [newDogId, setCreatedDogId] = useState(null); + const initializeStarted = useRef(false); + + useEffect(() => { + const initializeTestState = async () => { + if (initializeStarted.current) { + return; + } + // DataStore.clear() doesn't appear to reliably work in this scenario. + indexedDB.deleteDatabase('amplify-datastore'); + const { connectedToy } = await initializeTestData(); + setDogBiDirectionalToyId({ + toyBiDirectionalDogIdUpdated: (await connectedToy.BiDirectionalDog).id, + biDirectionalDogBiDirectionalToysIdUpdated: connectedToy.biDirectionalDogBiDirectionalToysId, + }); + setInitialized(true); + }; + + initializeTestState(); + initializeStarted.current = true; + }, []); + + if (!isInitialized) { + return null; + } + + return ( + + DataStoreFormCreateBidirectionalDog + + { + const results = await DataStore.query(BiDirectionalDog, (dog) => dog.name.eq('Spot')); + const createdDogId = results.pop()?.id ?? ''; + setCreatedDogId(createdDogId); + const stolenToy = (await DataStore.query(BiDirectionalToy, (toy) => toy.name.eq(toyName))).pop(); + if (stolenToy) { + setDogBiDirectionalToyId({ + toyBiDirectionalDogIdUpdated: (await stolenToy.BiDirectionalDog).id, + biDirectionalDogBiDirectionalToysIdUpdated: stolenToy.biDirectionalDogBiDirectionalToysId, + }); + } + }} + /> + + {DogBiDirectionalToyId.toyBiDirectionalDogIdUpdated === newDogId + ? 'Toy BiDirectionalDogId is connected to new dog' + : ''} + + + {DogBiDirectionalToyId.biDirectionalDogBiDirectionalToysIdUpdated === newDogId + ? 'Toy biDirectionalDogBiDirectionalToysId is connected to new dog' + : ''} + + + + ); +} diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/src/FormTests/DSCPKTeacher.tsx b/packages/test-generator/integration-test-templates-amplify-js-v6/src/FormTests/DSCPKTeacher.tsx new file mode 100644 index 000000000..f0059cb1f --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/src/FormTests/DSCPKTeacher.tsx @@ -0,0 +1,172 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import '@aws-amplify/ui-react/styles.css'; +import { ThemeProvider, View, Heading, Text, Divider } from '@aws-amplify/ui-react'; +import React, { useState, useEffect, useRef, SetStateAction } from 'react'; +import { DataStore } from '@aws-amplify/datastore'; +import { DataStoreFormCreateCPKTeacher, DataStoreFormUpdateCPKTeacher } from '../ui-components'; // eslint-disable-line import/extensions, max-len +import { + CPKStudent, + CPKClass, + CPKProject, + CPKTeacher, + LazyCPKTeacherCPKClass, + CPKTeacherCPKClass, + LazyCPKClass, + LazyCPKTeacher, +} from '../models'; +import { getModelsFromJoinTableRecords } from '../test-utils'; + +const initializeTestData = async (): Promise => { + await Promise.all([ + DataStore.save(new CPKStudent({ specialStudentId: 'Harry' })), + DataStore.save(new CPKStudent({ specialStudentId: 'Hermione' })), + DataStore.save(new CPKClass({ specialClassId: 'Math' })), + DataStore.save(new CPKClass({ specialClassId: 'English' })), + DataStore.save(new CPKProject({ specialProjectId: 'Either/Or' })), + DataStore.save(new CPKProject({ specialProjectId: 'Figure 8' })), + ]); +}; + +const initializeUpdate1TestData = async ({ + setDataStoreFormUpdateCPKTeacherRecordId, +}: { + setDataStoreFormUpdateCPKTeacherRecordId: React.Dispatch>; +}): Promise => { + const connectedStudent = await DataStore.query(CPKStudent, 'Harry'); + const connectedClass = await DataStore.query(CPKClass, 'Math'); + const connectedProject = await DataStore.query(CPKProject, 'Figure 8'); + + const createdRecord = await DataStore.save( + new CPKTeacher({ + specialTeacherId: 'Update1ID', + CPKStudent: connectedStudent as CPKStudent, + cPKTeacherCPKStudentSpecialStudentId: 'Harry', + }), + ); + + if (connectedClass) { + await DataStore.save( + new CPKTeacherCPKClass({ + cpkClass: connectedClass, + cpkTeacher: createdRecord, + }), + ); + } + + if (connectedProject) { + await DataStore.save( + CPKProject.copyOf(connectedProject, (updated) => { + Object.assign(updated, { cPKTeacherID: createdRecord.specialTeacherId }); + }), + ); + } + + setDataStoreFormUpdateCPKTeacherRecordId((prevId) => { + if (!prevId) { + return createdRecord.specialTeacherId; + } + return prevId; + }); +}; + +export default function () { + const [dataStoreFormCreateCPKTeacherResults, setDataStoreFormCreateCPKTeacherResults] = useState(''); + const [dataStoreFormUpdateCPKTeacherResults, setDataStoreFormUpdateCPKTeacherResults] = useState(''); + const [dataStoreFormUpdateCPKTeacherRecordId, setDataStoreFormUpdateCPKTeacherRecordId] = useState< + string | undefined + >(); + + const [isInitialized, setInitialized] = useState(false); + + const initializeStarted = useRef(false); + + useEffect(() => { + const initializeTestState = async () => { + if (initializeStarted.current) { + return; + } + // DataStore.clear() doesn't appear to reliably work in this scenario. + indexedDB.deleteDatabase('amplify-datastore'); + await initializeTestData(); + await Promise.all([initializeUpdate1TestData({ setDataStoreFormUpdateCPKTeacherRecordId })]); + setInitialized(true); + }; + + initializeTestState(); + initializeStarted.current = true; + }, []); + + if (!isInitialized) { + return null; + } + + return ( + + DataStoreFormCreateCPKTeacher + + { + const record = (await DataStore.query(CPKTeacher, 'Create1ID')) as LazyCPKTeacher; + + const CPKClasses = await getModelsFromJoinTableRecords( + record, + 'CPKClasses', + 'cpkClass', + ); + + setDataStoreFormCreateCPKTeacherResults( + JSON.stringify({ + ...record, + CPKStudent: await record.CPKStudent, + CPKClasses, + CPKProjects: await record.CPKProjects?.toArray(), + }), + ); + }} + /> + {dataStoreFormCreateCPKTeacherResults} + + + DataStoreFormUpdateCPKTeacher + + { + const record = (await DataStore.query(CPKTeacher, 'Update1ID')) as LazyCPKTeacher; + + const CPKClasses = await getModelsFromJoinTableRecords( + record, + 'CPKClasses', + 'cpkClass', + ); + + setDataStoreFormUpdateCPKTeacherResults( + JSON.stringify({ + ...record, + CPKStudent: await record.CPKStudent, + CPKClasses, + CPKProjects: await record.CPKProjects?.toArray(), + }), + ); + }} + /> + {dataStoreFormUpdateCPKTeacherResults} + + + + ); +} diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/src/FormTests/DSCar.tsx b/packages/test-generator/integration-test-templates-amplify-js-v6/src/FormTests/DSCar.tsx new file mode 100644 index 000000000..7b14db162 --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/src/FormTests/DSCar.tsx @@ -0,0 +1,96 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import '@aws-amplify/ui-react/styles.css'; +import React, { useState, useEffect, useRef, SetStateAction } from 'react'; +import { ThemeProvider, View, Heading, Text } from '@aws-amplify/ui-react'; +import { DataStore } from '@aws-amplify/datastore'; +import { DataStoreFormUpdateCar } from '../ui-components'; // eslint-disable-line import/extensions, max-len +import { Car, Dealership } from '../models'; + +const dataStoreFormUpdateCarName = 'Honda'; +const dataStoreFormUpdateCarPrevDealership = 'Oceans Fullerton'; + +const initializeTestData = async ({ + setDataStoreFormUpdateCarRecord, +}: { + setDataStoreFormUpdateCarRecord: React.Dispatch>; +}): Promise => { + let car = await DataStore.save(new Car({ name: dataStoreFormUpdateCarName })); + const [connectedDealership] = await Promise.all([ + DataStore.save(new Dealership({ name: dataStoreFormUpdateCarPrevDealership })), + DataStore.save(new Dealership({ name: 'Tustin Toyota' })), + DataStore.save(new Dealership({ name: 'Chapman Ford' })), + ]); + car = await DataStore.save( + Car.copyOf(car, (c) => { + Object.assign(c, { dealership: connectedDealership }); + }), + ); + setDataStoreFormUpdateCarRecord(car); +}; + +export default function () { + const [dataStoreFormUpdateCarResults, setDataStoreFormUpdateCarResults] = useState(''); + const [dataStoreFormUpdateCarRecord, setDataStoreFormUpdateCarRecord] = useState(); + const [isInitialized, setInitialized] = useState(false); + const initializeStarted = useRef(false); + + useEffect(() => { + const initializeTestState = async () => { + if (initializeStarted.current) { + return; + } + // DataStore.clear() doesn't appear to reliably work in this scenario. + indexedDB.deleteDatabase('amplify-datastore'); + await initializeTestData({ setDataStoreFormUpdateCarRecord }); + setInitialized(true); + }; + + initializeTestState(); + initializeStarted.current = true; + }, []); + + if (!isInitialized) { + return null; + } + + return ( + + DataStoreFormUpdateCar + + { + const record = (await DataStore.query(Car, (c) => c.name.eq(dataStoreFormUpdateCarName)))[0]; + const newDealershipCars = await (await record.dealership)?.cars.toArray(); + const prevDealershipCars = await ( + await DataStore.query(Dealership, (c) => c.name.eq(dataStoreFormUpdateCarPrevDealership)) + )[0]?.cars.toArray(); + setDataStoreFormUpdateCarResults( + JSON.stringify({ + ...record, + dealership: await record.dealership, + newDealershipHasCar: newDealershipCars?.some((c) => c.id === record.id), + prevDealershipDoesNotHaveCar: !prevDealershipCars?.some((c) => c.id === record.id), + }), + ); + }} + /> + {dataStoreFormUpdateCarResults} + + + ); +} diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/src/FormTests/DSCompositeDog.tsx b/packages/test-generator/integration-test-templates-amplify-js-v6/src/FormTests/DSCompositeDog.tsx new file mode 100644 index 000000000..9602b20ea --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/src/FormTests/DSCompositeDog.tsx @@ -0,0 +1,219 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { ThemeProvider, View, Heading, Text, Divider } from '@aws-amplify/ui-react'; +import React, { useState, useEffect, useRef, SetStateAction } from 'react'; +import { DataStore } from '@aws-amplify/datastore'; +import { + DataStoreFormUpdateCompositeDog, + DataStoreFormCreateCompositeDog, + DataStoreFormUpdateCompositeDogScalar, +} from '../ui-components'; // eslint-disable-line import/extensions, max-len +import { + CompositeDog, + CompositeOwner, + CompositeToy, + CompositeBowl, + CompositeVet, + LazyCompositeVet, + CompositeDogCompositeVet, + LazyCompositeDogCompositeVet, + LazyCompositeDog, +} from '../models'; +import { getModelsFromJoinTableRecords } from '../test-utils'; + +const initializeTestData = async (): Promise => { + await Promise.all([ + DataStore.save(new CompositeOwner({ lastName: 'Cooper', firstName: 'Dale' })), + DataStore.save(new CompositeOwner({ lastName: 'Cooper', firstName: 'Gordon' })), + DataStore.save(new CompositeToy({ kind: 'chew', color: 'green' })), + DataStore.save(new CompositeToy({ kind: 'chew', color: 'red' })), + DataStore.save(new CompositeBowl({ shape: 'round', size: 'xs' })), + DataStore.save(new CompositeBowl({ shape: 'round', size: 'xl' })), + DataStore.save(new CompositeVet({ specialty: 'Dentistry', city: 'Seattle' })), + DataStore.save(new CompositeVet({ specialty: 'Dentistry', city: 'Los Angeles' })), + ]); +}; + +const initializeUpdate1TestData = async ({ + setDataStoreFormUpdateCompositeDogRecord, +}: { + setDataStoreFormUpdateCompositeDogRecord: React.Dispatch>; +}): Promise => { + const connectedBowl = await DataStore.query(CompositeBowl, { shape: 'round', size: 'xs' }); + const connectedOwner = await DataStore.query(CompositeOwner, { lastName: 'Cooper', firstName: 'Dale' }); + const connectedVet = await DataStore.query(CompositeVet, { specialty: 'Dentistry', city: 'Seattle' }); + const connectedToy = await DataStore.query(CompositeToy, { kind: 'chew', color: 'green' }); + + const createdRecord = await DataStore.save( + new CompositeDog({ + name: 'Yundoo', + description: 'tiny but mighty', + CompositeBowl: connectedBowl, + CompositeOwner: connectedOwner, + }), + ); + + // connect vet through join table + if (connectedVet) { + await DataStore.save( + new CompositeDogCompositeVet({ + compositeDog: createdRecord, + compositeVet: connectedVet, + }), + ); + } + + // connect toy + if (connectedToy) { + await DataStore.save( + CompositeToy.copyOf(connectedToy, (updated) => { + Object.assign(updated, { + compositeDogCompositeToysName: createdRecord.name, + compositeDogCompositeToysDescription: createdRecord.description, + }); + }), + ); + } + + setDataStoreFormUpdateCompositeDogRecord((prevRecord) => { + if (!prevRecord) { + return createdRecord; + } + return prevRecord; + }); +}; + +export default function () { + const [dataStoreFormCreateCompositeDogResults, setDataStoreFormCreateCompositeDogResults] = useState(''); + const [dataStoreFormUpdateCompositeDogResults, setDataStoreFormUpdateCompositeDogResults] = useState(''); + const [dataStoreFormUpdateCompositeDogRecord, setDataStoreFormUpdateCompositeDogRecord] = useState< + CompositeDog | undefined + >(); + + const [isInitialized, setInitialized] = useState(false); + + const initializeStarted = useRef(false); + + useEffect(() => { + const initializeTestState = async () => { + if (initializeStarted.current) { + return; + } + // DataStore.clear() doesn't appear to reliably work in this scenario. + indexedDB.deleteDatabase('amplify-datastore'); + await initializeTestData(); + await initializeUpdate1TestData({ setDataStoreFormUpdateCompositeDogRecord }); + setInitialized(true); + }; + + initializeTestState(); + initializeStarted.current = true; + }, []); + + if (!isInitialized) { + return null; + } + + return ( + + DataStoreFormCreateCompositeDog + + { + const record = (await DataStore.query(CompositeDog, { + name: 'Cookie', + description: 'mogwai', + })) as LazyCompositeDog; + + const CompositeVets = await getModelsFromJoinTableRecords< + CompositeDog, + LazyCompositeVet, + LazyCompositeDogCompositeVet + >(record, 'CompositeVets', 'compositeVet'); + + setDataStoreFormCreateCompositeDogResults( + JSON.stringify({ + ...record, + CompositeBowl: await record.CompositeBowl, + CompositeOwner: await record.CompositeOwner, + CompositeToys: await record.CompositeToys?.toArray(), + CompositeVets, + }), + ); + }} + /> + + {dataStoreFormCreateCompositeDogResults} + + + DataStoreFormUpdateCompositeDog + + { + const record = (await DataStore.query(CompositeDog, { + name: 'Yundoo', + description: 'tiny but mighty', + })) as LazyCompositeDog; + + const CompositeVets = await getModelsFromJoinTableRecords< + CompositeDog, + LazyCompositeVet, + LazyCompositeDogCompositeVet + >(record, 'CompositeVets', 'compositeVet'); + + setDataStoreFormUpdateCompositeDogResults( + JSON.stringify({ + ...record, + CompositeBowl: await record.CompositeBowl, + CompositeOwner: await record.CompositeOwner, + CompositeToys: await record.CompositeToys?.toArray(), + CompositeVets, + }), + ); + }} + /> + {dataStoreFormUpdateCompositeDogResults} + + + DataStoreFormUpdateCompositeDogScalar + + { + const record = (await DataStore.query(CompositeDog, { + name: 'Yundoo', + description: 'tiny but mighty', + })) as LazyCompositeDog; + + setDataStoreFormUpdateCompositeDogResults( + JSON.stringify({ + ...record, + CompositeBowl: await record.CompositeBowl, + CompositeOwner: await record.CompositeOwner, + }), + ); + }} + /> + {dataStoreFormUpdateCompositeDogResults} + + DataStoreFormUpdateCompositeDogById + + + + + ); +} diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/src/FormTests/DSCompositeToy.tsx b/packages/test-generator/integration-test-templates-amplify-js-v6/src/FormTests/DSCompositeToy.tsx new file mode 100644 index 000000000..fc6583aa3 --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/src/FormTests/DSCompositeToy.tsx @@ -0,0 +1,90 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { ThemeProvider, View, Heading, Text } from '@aws-amplify/ui-react'; +import React, { useState, useEffect, useRef, SetStateAction } from 'react'; +import { DataStore } from '@aws-amplify/datastore'; +import { DataStoreFormUpdateCompositeToy } from '../ui-components'; // eslint-disable-line import/extensions, max-len +import { CompositeDog, CompositeToy } from '../models'; + +const initializeTestData = async (): Promise => { + await Promise.all([DataStore.save(new CompositeDog({ name: 'Yundoo', description: 'tiny but mighty' }))]); +}; + +const initializeUpdate1TestData = async ({ + setDataStoreFormUpdateCompositeToyRecord, +}: { + setDataStoreFormUpdateCompositeToyRecord: React.Dispatch>; +}): Promise => { + const createdRecord = await DataStore.save( + new CompositeToy({ + kind: 'chew', + color: 'red', + }), + ); + + setDataStoreFormUpdateCompositeToyRecord((prevRecord) => { + if (!prevRecord) { + return createdRecord; + } + return prevRecord; + }); +}; + +export default function () { + const [dataStoreFormUpdateCompositeToyResults, setDataStoreFormUpdateCompositeToyResults] = useState(''); + const [dataStoreFormUpdateCompositeToyRecord, setDataStoreFormUpdateCompositeToyRecord] = useState< + CompositeToy | undefined + >(); + const [isInitialized, setInitialized] = useState(false); + + const initializeStarted = useRef(false); + + useEffect(() => { + const initializeTestState = async () => { + if (initializeStarted.current) { + return; + } + // DataStore.clear() doesn't appear to reliably work in this scenario. + indexedDB.deleteDatabase('amplify-datastore'); + await initializeTestData(); + await Promise.all([initializeUpdate1TestData({ setDataStoreFormUpdateCompositeToyRecord })]); + setInitialized(true); + }; + + initializeTestState(); + initializeStarted.current = true; + }, []); + + if (!isInitialized) { + return null; + } + + return ( + + DataStoreFormUpdateCompositeToy + + { + const record = await DataStore.query(CompositeToy, { kind: 'chew', color: 'red' }); + setDataStoreFormUpdateCompositeToyResults(JSON.stringify(record)); + }} + /> + {dataStoreFormUpdateCompositeToyResults} + + + ); +} diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/src/FormTests/DSDealership.tsx b/packages/test-generator/integration-test-templates-amplify-js-v6/src/FormTests/DSDealership.tsx new file mode 100644 index 000000000..b85a6895a --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/src/FormTests/DSDealership.tsx @@ -0,0 +1,93 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import '@aws-amplify/ui-react/styles.css'; +import React, { useState, useEffect, useRef, SetStateAction } from 'react'; +import { ThemeProvider, View, Heading, Text } from '@aws-amplify/ui-react'; +import { DataStore } from '@aws-amplify/datastore'; +import { DataStoreFormUpdateDealership } from '../ui-components'; // eslint-disable-line import/extensions, max-len +import { Car, Dealership } from '../models'; + +const dataStoreFormUpdateDealershipName = 'Oceans Fullerton'; +const dataStoreFormUpdateDealershipPrevCarName = 'Honda'; + +const initializeTestData = async ({ + setDataStoreFormUpdateDealershipRecord, +}: { + setDataStoreFormUpdateDealershipRecord: React.Dispatch>; +}): Promise => { + const dealership = await DataStore.save(new Dealership({ name: dataStoreFormUpdateDealershipName })); + + await Promise.all([ + DataStore.save(new Car({ name: dataStoreFormUpdateDealershipPrevCarName, dealership })), + DataStore.save(new Car({ name: 'Toyota' })), + DataStore.save(new Car({ name: 'Ford' })), + ]); + + setDataStoreFormUpdateDealershipRecord(dealership); +}; + +export default function () { + const [dataStoreFormUpdateDealershipResults, setDataStoreFormUpdateDealershipResults] = useState(''); + const [dataStoreFormUpdateDealershipRecord, setDataStoreFormUpdateDealershipRecord] = useState< + Dealership | undefined + >(); + const [isInitialized, setInitialized] = useState(false); + const initializeStarted = useRef(false); + + useEffect(() => { + const initializeTestState = async () => { + if (initializeStarted.current) { + return; + } + // DataStore.clear() doesn't appear to reliably work in this scenario. + indexedDB.deleteDatabase('amplify-datastore'); + await initializeTestData({ setDataStoreFormUpdateDealershipRecord }); + setInitialized(true); + }; + + initializeTestState(); + initializeStarted.current = true; + }, []); + + if (!isInitialized) { + return null; + } + + return ( + + DataStoreFormUpdateDealership + + { + const record = (await DataStore.query(Dealership, (d) => d.name.eq(dataStoreFormUpdateDealershipName)))[0]; + const recordCars = await record.cars?.toArray(); + const prevCar = (await DataStore.query(Car, (c) => c.name.eq(dataStoreFormUpdateDealershipPrevCarName)))[0]; + setDataStoreFormUpdateDealershipResults( + JSON.stringify({ + ...record, + cars: recordCars, + newCarBelongsToDealership: recordCars[0].dealershipId === record.id, + prevCarDoesNotBelongToDealership: prevCar.dealershipId !== record.id, + }), + ); + }} + /> + {dataStoreFormUpdateDealershipResults} + + + ); +} diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/src/FormTests/DSModelWithVariableCollisions.tsx b/packages/test-generator/integration-test-templates-amplify-js-v6/src/FormTests/DSModelWithVariableCollisions.tsx new file mode 100644 index 000000000..877e97485 --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/src/FormTests/DSModelWithVariableCollisions.tsx @@ -0,0 +1,71 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import '@aws-amplify/ui-react/styles.css'; +import { ThemeProvider, View, Heading, Text } from '@aws-amplify/ui-react'; +import { useState, useEffect, useRef } from 'react'; +import { DataStore } from '@aws-amplify/datastore'; +// eslint-disable-next-line import/extensions +import { DataStoreFormUpdateModelWithVariableCollisions } from '../ui-components'; +import { ModelWithVariableCollisions } from '../models'; + +const initializeTestData = async () => { + const savedRecord = DataStore.save(new ModelWithVariableCollisions({ modelWithVariableCollisions: 'test' })); + return savedRecord; +}; + +export default function DSModelWithVariableCollisions() { + const [isInitialized, setInitialized] = useState(false); + const [recordId, setRecordId] = useState(''); + const [updatedFieldValue, setUpdatedFieldValue] = useState(''); + // setting to null to prevent matching empty string in the test + const initializeStarted = useRef(false); + + useEffect(() => { + const initializeTestState = async () => { + if (initializeStarted.current) { + return; + } + // DataStore.clear() doesn't appear to reliably work in this scenario. + indexedDB.deleteDatabase('amplify-datastore'); + const createdRecord = await initializeTestData(); + setRecordId(createdRecord.id); + setInitialized(true); + }; + + initializeTestState(); + initializeStarted.current = true; + }, []); + + if (!isInitialized) { + return null; + } + + return ( + + DataStoreFormUpdateModelWithVariableCollisions + + { + const updatedRecord = await DataStore.query(ModelWithVariableCollisions, recordId); + setUpdatedFieldValue(updatedRecord?.modelWithVariableCollisions); + }} + /> + UpdatedField= {updatedFieldValue} + + + ); +} diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/src/FormTests/index.tsx b/packages/test-generator/integration-test-templates-amplify-js-v6/src/FormTests/index.tsx new file mode 100644 index 000000000..4d7567526 --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/src/FormTests/index.tsx @@ -0,0 +1,104 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { Link, useParams } from 'react-router-dom'; +import CustomDog from './CustomDog'; +import CustomNestedJSON from './CustomNestedJSON'; +import DSAllSupportedFormFields from './DSAllSupportedFormFields'; +import DSCompositeDog from './DSCompositeDog'; +import DSCompositeToy from './DSCompositeToy'; +import DSCPKTeacher from './DSCPKTeacher'; +import DSBidirectionalOwner from './DSBidirectionalOwner'; +import DSBidirectionalDog from './DSBidirectionalDog'; +import DSBidirectionalToy from './DSBidirectionalToy'; +import DSModelWithVariableCollisions from './DSModelWithVariableCollisions'; +import DSCar from './DSCar'; +import DSDealership from './DSDealership'; + +export default function FormTests() { + const { subject } = useParams(); + + switch (subject) { + case 'CustomDog': + return ; + case 'CustomNestedJSON': + return ; + case 'DSAllSupportedFormFields': + return ; + case 'DSCompositeDog': + return ; + case 'DSCompositeToy': + return ; + case 'DSCPKTeacher': + return ; + case 'DSBidirectionalDog': + return ; + case 'DSBidirectionalOwner': + return ; + case 'DSBidirectionalToy': + return ; + case 'DSModelWithVariableCollisions': + return ; + case 'DSCar': + return ; + case 'DSDealership': + return ; + + default: + return ( +
+

Codegen UI Form Functional Tests

+
    +
  • + CustomDog +
  • +
  • + CustomNestedJSON +
  • +
  • + DSAllSupportedFormFields +
  • +
  • + DSCompositeDog +
  • +
  • + DSCompositeToy +
  • +
  • + DSCPKTeacher +
  • +
  • + DSBidirectionalDog +
  • +
  • + DSBidirectionalOwner +
  • +
  • + DSBidirectionalToy +
  • +
  • + DSModelWithVariableCollisions +
  • +
  • + DSCar +
  • +
  • + DSDealership +
  • +
+
+ ); + } +} diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/src/GenerateTests.css b/packages/test-generator/integration-test-templates-amplify-js-v6/src/GenerateTests.css new file mode 100644 index 000000000..2008502b7 --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/src/GenerateTests.css @@ -0,0 +1,6 @@ +table, +th, +td { + border: 1px solid black; + border-collapse: collapse; +} diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/src/GenerateTests.tsx b/packages/test-generator/integration-test-templates-amplify-js-v6/src/GenerateTests.tsx new file mode 100644 index 000000000..61a08cee9 --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/src/GenerateTests.tsx @@ -0,0 +1,159 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import './GenerateTests.css'; +import { useState, useEffect, useCallback } from 'react'; +import { BrowserTestGenerator, TestCase } from '@aws-amplify/codegen-ui-test-generator'; +import { InvalidInputError, InternalError } from '@aws-amplify/codegen-ui'; +import { ScriptKind, ScriptTarget } from 'typescript'; + +const defaultGenerator = new BrowserTestGenerator({ + writeToLogger: true, + writeToDisk: false, +}); + +const generatorConfigs = { + ES2016_TSX: { + target: ScriptTarget.ES2016, + script: ScriptKind.TSX, + }, + ES2016_JSX: { + target: ScriptTarget.ES2016, + script: ScriptKind.JSX, + }, + ES5_TSX: { + target: ScriptTarget.ES5, + script: ScriptKind.TSX, + }, + ES5_JSX: { + target: ScriptTarget.ES5, + script: ScriptKind.JSX, + }, +}; + +const generators = Object.fromEntries( + Object.entries(generatorConfigs).map(([generatorTarget, renderConfigOverride]) => { + return [ + generatorTarget, + new BrowserTestGenerator({ + writeToLogger: true, + writeToDisk: false, + renderConfigOverride, + immediatelyThrowGenerateErrors: true, + }), + ]; + }), +); + +const targetValues = Object.keys(generators); + +type GenerateTestCaseProps = { + testCase: TestCase; + executeImmediately: boolean; +}; + +type TestResult = { + result: 'PASSED' | 'FAILED'; + errorType?: any; +}; + +const GenerateTestCase = (props: GenerateTestCaseProps) => { + const { testCase, executeImmediately } = props; + const { name, testType } = testCase; + const [testState, setTestState] = useState<{ [index: string]: TestResult }>({}); + + const generateComponent = useCallback(() => { + Object.entries(generators).forEach(([targetName, generator]) => { + try { + generator.generate([testCase]); + setTestState((previousTestState) => { + const clonedState = { ...previousTestState }; + clonedState[targetName] = { result: 'PASSED' }; + return clonedState; + }); + } catch (e) { + // eslint-disable-next-line no-console + console.error(`Generate error for: ${name} - ${targetName}`, e); + const errorType = e instanceof InvalidInputError || e instanceof InternalError ? e.constructor.name : e; + setTestState((previousTestState) => { + const clonedState = { ...previousTestState }; + clonedState[targetName] = { result: 'FAILED', errorType }; + return clonedState; + }); + } + }); + }, [name, testCase]); + + useEffect(() => { + if (executeImmediately) { + generateComponent(); + } + }, [executeImmediately, generateComponent]); + + return ( + + + + + {name} + {testType} + {targetValues.map((targetName) => ( + + {testState[targetName] && + (testState[targetName].result === 'PASSED' ? ' ✅' : ` ❌ - ${testState[targetName].errorType}`)} + + ))} + + ); +}; + +export default function GenerateTests() { + const [generateAll, setGenerateAll] = useState(false); + const testCases = defaultGenerator.getTestCases(); + // only test snippet with 4 components for performance + const snippetTestCases: TestCase[] = testCases + .filter((testCase) => testCase.testType === 'Component') + .slice(0, 4) + .map((testCase) => ({ ...testCase, testType: 'Snippet' })); + const testCasesWithSnippets = testCases.concat(snippetTestCases); + const testCaseComponents = testCasesWithSnippets.map((testCase) => ( + + )); + return ( +
+

Browser Generation Tests

+ + + + + + + {targetValues.map((targetName) => ( + + ))} + + {testCaseComponents} +
GenerateTest NameTest Type{targetName}
+
+
+ ); +} diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/src/PrimitivesTests.tsx b/packages/test-generator/integration-test-templates-amplify-js-v6/src/PrimitivesTests.tsx new file mode 100644 index 000000000..d6f656bb1 --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/src/PrimitivesTests.tsx @@ -0,0 +1,254 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { useState, useRef, useEffect } from 'react'; + +import { Heading, View } from '@aws-amplify/ui-react'; +import { + AlertPrimitive, + BadgePrimitive, + ButtonPrimitive, + ButtonGroupPrimitive, + CardPrimitive, + CheckboxFieldPrimitive, + CollectionPrimitive, + DividerPrimitive, + FlexPrimitive, + GridPrimitive, + HeadingPrimitive, + IconPrimitive, + ImagePrimitive, + LinkPrimitive, + LoaderPrimitive, + MenuButtonPrimitive, + MenuPrimitive, + PaginationPrimitive, + PasswordFieldPrimitive, + PhoneNumberFieldPrimitive, + PlaceholderPrimitive, + RadioPrimitive, + RadioGroupFieldPrimitive, + RatingPrimitive, + ScrollViewPrimitive, + SearchFieldPrimitive, + SelectFieldPrimitive, + SliderFieldPrimitive, + StepperFieldPrimitive, + SwitchFieldPrimitive, + // TabsPrimitive, + TablePrimitive, + TextPrimitive, + TextAreaFieldPrimitive, + TextFieldPrimitive, + ToggleButtonPrimitive, + ToggleButtonGroupPrimitive, + ViewPrimitive, + VisuallyHiddenPrimitive, + // eslint-disable-next-line import/extensions +} from './ui-components'; +import { initializeListingTestData } from './mock-utils'; + +export default function PrimitivesTests() { + const [isInitialized, setInitialized] = useState(false); + const initializeStarted = useRef(false); + + useEffect(() => { + const initializeTestUserData = async () => { + if (initializeStarted.current) { + return; + } + // DataStore.clear() doesn't appear to reliably work in this scenario. + const ddbRequest = indexedDB.deleteDatabase('amplify-datastore'); + await new Promise((res, rej) => { + ddbRequest.onsuccess = () => { + res(true); + }; + ddbRequest.onerror = () => { + rej(ddbRequest.error); + }; + }); + await Promise.all([initializeListingTestData()]); + setInitialized(true); + }; + + initializeTestUserData(); + initializeStarted.current = true; + }, []); + + if (!isInitialized) { + return null; + } + + return ( + <> + + Alert + + + + Badge + + + + Button + + + + Button Group + + + + Card + + + + Checkbox Field + + + + Collection + + + + Divider + + + + Flex + + + + Grid + + + + Heading + + + + Icon + + + + Image + + + + Link + + + + Loader + + + + Menu + + + + Menu Button + + + + Pagination + + + + Password Field + + + + Phone Number Field + + + + Placeholder + + + + Radio + + + + Radio Group Field + + + + Rating + + + + Scroll View + + + + Search Field + + + + Select Field + + + + Slider Field + + + + Stepper Field + + + + Switch Field + + + {/* + Tabs + + */} + + Table + + + + Text + + + + Text Area Field + + + + Text Field + + + + Toggle Button + + + + Toggle Button Group + + + + View + + + + Visually Hidden + + + + ); +} diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/src/TwoWayBindingTests.tsx b/packages/test-generator/integration-test-templates-amplify-js-v6/src/TwoWayBindingTests.tsx new file mode 100644 index 000000000..6be7c438d --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/src/TwoWayBindingTests.tsx @@ -0,0 +1,41 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import '@aws-amplify/ui-react/styles.css'; +import { ThemeProvider } from '@aws-amplify/ui-react'; +import { TwoWayBindings } from './ui-components'; // eslint-disable-line import/extensions + +export default function TwoWayBindingTests() { + return ( + + + + ); +} diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/src/WorkflowTests.tsx b/packages/test-generator/integration-test-templates-amplify-js-v6/src/WorkflowTests.tsx new file mode 100644 index 000000000..771c5a0d1 --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/src/WorkflowTests.tsx @@ -0,0 +1,246 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { useState, SyntheticEvent, useEffect, useRef } from 'react'; +import '@aws-amplify/ui-react/styles.css'; +import { ThemeProvider, View, Heading, Divider, Button } from '@aws-amplify/ui-react'; +import { Hub } from 'aws-amplify/utils'; +import { DataStore } from '@aws-amplify/datastore'; +import { useDataStoreBinding } from './ui-components/utils'; // eslint-disable-line import/extensions +import { ComplexModel, User } from './models'; +import { + AuthSignOutActions, + Event, + NavigationActions, + InternalMutation, + MutationWithSyntheticProp, + SetStateWithoutInitialValue, + UpdateVisibility, + DataStoreActions, + FormWithState, + SimpleUserCollection, + InputMutationOnClick, + ConditionalInMutation, + CreateModelWithComplexTypes, +} from './ui-components'; // eslint-disable-line import/extensions + +type AuthState = 'LoggedIn' | 'LoggedOutLocally' | 'LoggedOutGlobally' | 'Error'; + +export default function ComplexTests() { + const [clicked, click] = useState(''); + const [doubleclicked, doubleclick] = useState(''); + const [mouseddown, mousedown] = useState(''); + const [mouseentered, mouseenter] = useState(''); + const [mouseleft, mouseleave] = useState(''); + const [mousemoved, mousemove] = useState(''); + const [mousedout, mouseout] = useState(''); + const [mousedover, mouseover] = useState(''); + const [mousedup, mouseup] = useState(''); + const [changed, change] = useState(''); + const [inputted, input] = useState(''); + const [focused, focus] = useState(''); + const [blurred, blur] = useState(''); + const [keyeddown, keydown] = useState(''); + const [keypressed, keypress] = useState(''); + const [keyedup, keyup] = useState(''); + const [isInitialized, setInitialized] = useState(false); + const [idToDelete, setIdToDelete] = useState(''); + const [idToUpdate, setIdToUpdate] = useState(''); + const [hasDisappeared, setDisappeared] = useState(false); + const [authState, setAuthState] = useState('LoggedIn'); + const [formIdToUpdate, setFormIdToUpdate] = useState(''); + const initializeStarted = useRef(false); + + const initializeUserTestData = async (): Promise => { + await DataStore.save(new User({ firstName: 'DeleteMe', lastName: 'Me' })); + await DataStore.save(new User({ firstName: 'UpdateMe', lastName: 'Me' })); + await DataStore.save(new User({ firstName: 'FormUpdate', lastName: 'Me' })); + }; + + const initializeAuthListener = () => { + Hub.listen('ui', (message: any) => { + const { event, data } = message && message.payload; + if (event === 'actions:auth:signout:finished') { + const { + options: { global }, + } = data; + if (global === true) { + setAuthState('LoggedOutGlobally'); + } else if (global !== undefined && global !== null && global === false) { + setAuthState('LoggedOutLocally'); + } else { + setAuthState('Error'); + } + } + }); + }; + + useEffect(() => { + const initializeTestState = async () => { + if (initializeStarted.current) { + return; + } + // DataStore.clear() doesn't appear to reliably work in this scenario. + indexedDB.deleteDatabase('amplify-datastore').onsuccess = async function () { + await initializeUserTestData(); + const queriedIdToDelete = (await DataStore.query(User, (criteria) => criteria.firstName.eq('DeleteMe')))[0].id; + setIdToDelete(queriedIdToDelete); + const queriedIdToUpdate = (await DataStore.query(User, (criteria) => criteria.firstName.eq('UpdateMe')))[0].id; + setIdToUpdate(queriedIdToUpdate); + const queriedFormIdToUpdate = ( + await DataStore.query(User, (criteria) => criteria.firstName.eq('FormUpdate')) + )[0].id; + setFormIdToUpdate(queriedFormIdToUpdate); + initializeAuthListener(); + setInitialized(true); + }; + }; + + initializeTestState(); + initializeStarted.current = true; + }, []); + + const complexModels = useDataStoreBinding({ + type: 'collection', + model: ComplexModel, + }) as any; + + if (!isInitialized) { + return null; + } + + return ( + + + Events + { + click('✅'); + }} + clicked={clicked} + onDoubleClick={() => { + doubleclick('✅'); + }} + doubleclicked={doubleclicked} + onMouseDown={() => { + mousedown('✅'); + }} + mouseddown={mouseddown} + onMouseEnter={() => { + mouseenter('✅'); + }} + mouseentered={mouseentered} + onMouseLeave={() => { + mouseleave('✅'); + }} + mouseleft={mouseleft} + onMouseMove={() => { + mousemove('✅'); + }} + mousemoved={mousemoved} + onMouseOut={() => { + mouseout('✅'); + }} + mousedout={mousedout} + onMouseOver={() => { + mouseover('✅'); + }} + mousedover={mousedover} + onMouseUp={() => { + mouseup('✅'); + }} + mousedup={mousedup} + onChange={(event: SyntheticEvent) => { + change((event.target as HTMLInputElement).value); + }} + changed={changed} + onInput={(event: SyntheticEvent) => { + input((event.target as HTMLInputElement).value); + }} + inputted={inputted} + onFocus={() => { + focus('✅'); + }} + focused={focused} + onBlur={() => { + blur('✅'); + }} + blurred={blurred} + onKeyDown={() => { + keydown('✅'); + }} + keyeddown={keyeddown} + onKeyPress={() => { + keypress('✅'); + }} + keypressed={keypressed} + onKeyUp={() => { + keyup('✅'); + }} + keyedup={keyedup} + /> + + + + Navigation Actions + + {!hasDisappeared && ( + + )} + + + + Auth Actions + + {authState} + + + + DataStore Actions + + + + + + State Actions + + + + + Mutation Actions + + + + + + + + + + + {complexModels && complexModels.items && complexModels.items.length === 1 && ( + {JSON.stringify(complexModels.items[0])} + )} + + + ); +} diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/src/mock-utils.ts b/packages/test-generator/integration-test-templates-amplify-js-v6/src/mock-utils.ts new file mode 100644 index 000000000..a0f74347a --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/src/mock-utils.ts @@ -0,0 +1,117 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { Buffer } from 'buffer'; +import { DataStore } from '@aws-amplify/datastore'; +import { Listing } from './models'; + +const TEST_REGION = 'us-west-2'; +const TEST_POOL_NAME = 'testpool'; +const TEST_USER_NAME = 'testuser'; +const MOCK_ENDPOINT = 'https://fake-appsync-endpoint/graphql'; + +export const DATA_STORE_MOCK_EXPORTS = { + endpoint: MOCK_ENDPOINT, + defaultAuthMode: 'none' as const, +}; + +export const AUTH_MOCK_EXPORTS = { + userPoolId: `${TEST_REGION}_${TEST_POOL_NAME}`, + userPoolClientId: TEST_POOL_NAME, +}; + +export const initializeAuthMockData = (authAttributes: Record) => { + const buildAuthKey = (key: string) => `CognitoIdentityServiceProvider.${TEST_POOL_NAME}.${TEST_USER_NAME}.${key}`; + const generateJwt = (tokenData: any) => `.${Buffer.from(JSON.stringify(tokenData), 'utf8').toString('base64')}`; + + // Set expiry to 24h in the future + const expiryTimestamp = Math.floor(Date.now() / 1000) + 86400; + + const userAttributes = Object.entries(authAttributes).map(([attrName, attrValue]) => { + return { Name: attrName, Value: attrValue }; + }); + + localStorage.setItem(`CognitoIdentityServiceProvider.${TEST_POOL_NAME}.LastAuthUser`, TEST_USER_NAME); + localStorage.setItem(buildAuthKey('userData'), JSON.stringify({ UserAttributes: userAttributes })); + localStorage.setItem( + buildAuthKey('accessToken'), + generateJwt({ + scope: 'aws.cognito.signin.user.admin', + exp: expiryTimestamp, + }), + ); + localStorage.setItem( + buildAuthKey('idToken'), + generateJwt({ + exp: expiryTimestamp, + }), + ); +}; +export const initializeListingTestData = async (): Promise => { + await DataStore.save( + new Listing({ title: 'Cozy Bungalow', priceUSD: 1500, description: 'Lorem ipsum dolor sit amet' }), + ); + await DataStore.save( + new Listing({ + title: 'Mountain Retreat', + priceUSD: 1800, + description: 'consectetur adipiscing elit, sed do eiusmod tempor incididunt', + }), + ); + await DataStore.save( + new Listing({ + title: 'Quiet Cottage', + priceUSD: 1100, + description: 'ut labore et dolore magna aliqua. Ut enim ad minim veniam', + }), + ); + await DataStore.save( + new Listing({ + title: 'Creekside Hideaway', + priceUSD: 950, + description: 'quis nostrud exercitation ullamco laboris nisi ut aliquip', + }), + ); + await DataStore.save( + new Listing({ + title: 'Cabin in the Woods', + priceUSD: 600, + description: 'ex ea commodo consequat. Duis aute irure dolor in reprehenderit', + }), + ); + await DataStore.save( + new Listing({ + title: 'Cabin at the Lake (unit 1)', + priceUSD: 700, + description: 'in voluptate velit esse cillum dolore eu fugiat nulla pariatur', + }), + ); + await DataStore.save( + new Listing({ + title: 'Cabin at the Lake (unit 2)', + priceUSD: 800, + description: 'Excepteur sint occaecat cupidatat non proident', + }), + ); + await DataStore.save( + new Listing({ + title: 'Beachside Cottage', + priceUSD: 1000, + description: 'sunt in culpa qui officia deserunt mollit anim id est laborum', + }), + ); + await DataStore.save(new Listing({ title: 'Lush Resort', priceUSD: 3500, description: 'Its real nice' })); + await DataStore.save(new Listing({ title: 'Chalet away from home', priceUSD: 5000, description: 'youll like it' })); +}; diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/src/models/index.d.ts b/packages/test-generator/integration-test-templates-amplify-js-v6/src/models/index.d.ts new file mode 100644 index 000000000..23e6476e0 --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/src/models/index.d.ts @@ -0,0 +1,1027 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +/* eslint-disable @typescript-eslint/no-redeclare */ +/* eslint-disable import/no-duplicates */ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +import { + ModelInit, + MutableModel, + __modelMeta__, + ManagedIdentifier, + CustomIdentifier, + CompositeIdentifier, +} from '@aws-amplify/datastore'; +// @ts-ignore +import { LazyLoading, LazyLoadingDisabled, AsyncCollection, AsyncItem } from '@aws-amplify/datastore'; + +export enum City { + SAN_FRANCISCO = 'SAN_FRANCISCO', + NEW_YORK = 'NEW_YORK', + HOUSTON = 'HOUSTON', + AUSTIN = 'AUSTIN', + LOS_ANGELES = 'LOS_ANGELES', + CHICAGO = 'CHICAGO', + SAN_DIEGO = 'SAN_DIEGO', + NEW_HAVEN = 'NEW_HAVEN', + PORTLAND = 'PORTLAND', + SEATTLE = 'SEATTLE', +} + +type EagerCustomType = { + readonly StringVal?: string | null; + readonly NumVal?: number | null; + readonly BoolVal?: boolean | null; +}; + +type LazyCustomType = { + readonly StringVal?: string | null; + readonly NumVal?: number | null; + readonly BoolVal?: boolean | null; +}; + +export declare type CustomType = LazyLoading extends LazyLoadingDisabled ? EagerCustomType : LazyCustomType; + +export declare const CustomType: new (init: ModelInit) => CustomType; + +type EagerUserPreference = { + readonly [__modelMeta__]: { + identifier: ManagedIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly id: string; + readonly favoriteColor?: string | null; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; +}; + +type LazyUserPreference = { + readonly [__modelMeta__]: { + identifier: ManagedIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly id: string; + readonly favoriteColor?: string | null; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; +}; + +export declare type UserPreference = LazyLoading extends LazyLoadingDisabled ? EagerUserPreference : LazyUserPreference; + +export declare const UserPreference: (new (init: ModelInit) => UserPreference) & { + copyOf( + source: UserPreference, + mutator: (draft: MutableModel) => MutableModel | void, + ): UserPreference; +}; + +type EagerUser = { + readonly [__modelMeta__]: { + identifier: ManagedIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly id: string; + readonly firstName?: string | null; + readonly lastName?: string | null; + readonly age?: number | null; + readonly isLoggedIn?: boolean | null; + readonly loggedInColor?: string | null; + readonly loggedOutColor?: string | null; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; +}; + +type LazyUser = { + readonly [__modelMeta__]: { + identifier: ManagedIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly id: string; + readonly firstName?: string | null; + readonly lastName?: string | null; + readonly age?: number | null; + readonly isLoggedIn?: boolean | null; + readonly loggedInColor?: string | null; + readonly loggedOutColor?: string | null; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; +}; + +export declare type User = LazyLoading extends LazyLoadingDisabled ? EagerUser : LazyUser; + +export declare const User: (new (init: ModelInit) => User) & { + copyOf(source: User, mutator: (draft: MutableModel) => MutableModel | void): User; +}; + +type EagerListing = { + readonly [__modelMeta__]: { + identifier: ManagedIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly id: string; + readonly title?: string | null; + readonly priceUSD?: number | null; + readonly description?: string | null; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; +}; + +type LazyListing = { + readonly [__modelMeta__]: { + identifier: ManagedIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly id: string; + readonly title?: string | null; + readonly priceUSD?: number | null; + readonly description?: string | null; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; +}; + +export declare type Listing = LazyLoading extends LazyLoadingDisabled ? EagerListing : LazyListing; + +export declare const Listing: (new (init: ModelInit) => Listing) & { + copyOf(source: Listing, mutator: (draft: MutableModel) => MutableModel | void): Listing; +}; + +type EagerComplexModel = { + readonly [__modelMeta__]: { + identifier: ManagedIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly id: string; + readonly listElement: (string | null)[]; + readonly myCustomField?: CustomType | null; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; +}; + +type LazyComplexModel = { + readonly [__modelMeta__]: { + identifier: ManagedIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly id: string; + readonly listElement: (string | null)[]; + readonly myCustomField?: CustomType | null; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; +}; + +export declare type ComplexModel = LazyLoading extends LazyLoadingDisabled ? EagerComplexModel : LazyComplexModel; + +export declare const ComplexModel: (new (init: ModelInit) => ComplexModel) & { + copyOf( + source: ComplexModel, + mutator: (draft: MutableModel) => MutableModel | void, + ): ComplexModel; +}; + +type EagerClass = { + readonly [__modelMeta__]: { + identifier: ManagedIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly id: string; + readonly name?: string | null; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; +}; + +type LazyClass = { + readonly [__modelMeta__]: { + identifier: ManagedIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly id: string; + readonly name?: string | null; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; +}; + +export declare type Class = LazyLoading extends LazyLoadingDisabled ? EagerClass : LazyClass; + +export declare const Class: (new (init: ModelInit) => Class) & { + copyOf(source: Class, mutator: (draft: MutableModel) => MutableModel | void): Class; +}; + +type EagerTag = { + readonly [__modelMeta__]: { + identifier: ManagedIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly id: string; + readonly label?: string | null; + readonly AllSupportedFormFields?: (AllSupportedFormFieldsTag | null)[] | null; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; +}; + +type LazyTag = { + readonly [__modelMeta__]: { + identifier: ManagedIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly id: string; + readonly label?: string | null; + readonly AllSupportedFormFields: AsyncCollection; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; +}; + +export declare type Tag = LazyLoading extends LazyLoadingDisabled ? EagerTag : LazyTag; + +export declare const Tag: (new (init: ModelInit) => Tag) & { + copyOf(source: Tag, mutator: (draft: MutableModel) => MutableModel | void): Tag; +}; + +type EagerAllSupportedFormFields = { + readonly [__modelMeta__]: { + identifier: ManagedIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly id: string; + readonly string?: string | null; + readonly stringArray?: (string | null)[] | null; + readonly int?: number | null; + readonly float?: number | null; + readonly awsDate?: string | null; + readonly awsTime?: string | null; + readonly awsDateTime?: string | null; + readonly awsTimestamp?: number | null; + readonly awsEmail?: string | null; + readonly awsUrl?: string | null; + readonly awsIPAddress?: string | null; + readonly boolean?: boolean | null; + readonly awsJson?: string | null; + readonly awsPhone?: string | null; + readonly enum?: City | keyof typeof City | null; + readonly nonModelField?: CustomType | null; + readonly nonModelFieldArray?: (CustomType | null)[] | null; + readonly HasOneUser?: User | null; + readonly BelongsToOwner?: Owner | null; + readonly HasManyStudents?: (Student | null)[] | null; + readonly ManyToManyTags?: (AllSupportedFormFieldsTag | null)[] | null; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; + readonly allSupportedFormFieldsHasOneUserId?: string | null; + readonly allSupportedFormFieldsBelongsToOwnerId?: string | null; +}; + +type LazyAllSupportedFormFields = { + readonly [__modelMeta__]: { + identifier: ManagedIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly id: string; + readonly string?: string | null; + readonly stringArray?: (string | null)[] | null; + readonly int?: number | null; + readonly float?: number | null; + readonly awsDate?: string | null; + readonly awsTime?: string | null; + readonly awsDateTime?: string | null; + readonly awsTimestamp?: number | null; + readonly awsEmail?: string | null; + readonly awsUrl?: string | null; + readonly awsIPAddress?: string | null; + readonly boolean?: boolean | null; + readonly awsJson?: string | null; + readonly awsPhone?: string | null; + readonly enum?: City | keyof typeof City | null; + readonly nonModelField?: CustomType | null; + readonly nonModelFieldArray?: (CustomType | null)[] | null; + readonly HasOneUser: AsyncItem; + readonly BelongsToOwner: AsyncItem; + readonly HasManyStudents: AsyncCollection; + readonly ManyToManyTags: AsyncCollection; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; + readonly allSupportedFormFieldsHasOneUserId?: string | null; + readonly allSupportedFormFieldsBelongsToOwnerId?: string | null; +}; + +export declare type AllSupportedFormFields = LazyLoading extends LazyLoadingDisabled + ? EagerAllSupportedFormFields + : LazyAllSupportedFormFields; + +export declare const AllSupportedFormFields: (new ( + init: ModelInit, +) => AllSupportedFormFields) & { + copyOf( + source: AllSupportedFormFields, + mutator: (draft: MutableModel) => MutableModel | void, + ): AllSupportedFormFields; +}; + +type EagerOwner = { + readonly [__modelMeta__]: { + identifier: ManagedIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly id: string; + readonly name?: string | null; + readonly AllSupportedFormFields?: AllSupportedFormFields | null; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; + readonly ownerAllSupportedFormFieldsId?: string | null; +}; + +type LazyOwner = { + readonly [__modelMeta__]: { + identifier: ManagedIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly id: string; + readonly name?: string | null; + readonly AllSupportedFormFields: AsyncItem; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; + readonly ownerAllSupportedFormFieldsId?: string | null; +}; + +export declare type Owner = LazyLoading extends LazyLoadingDisabled ? EagerOwner : LazyOwner; + +export declare const Owner: (new (init: ModelInit) => Owner) & { + copyOf(source: Owner, mutator: (draft: MutableModel) => MutableModel | void): Owner; +}; + +type EagerStudent = { + readonly [__modelMeta__]: { + identifier: ManagedIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly id: string; + readonly name?: string | null; + readonly allSupportedFormFieldsID?: string | null; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; +}; + +type LazyStudent = { + readonly [__modelMeta__]: { + identifier: ManagedIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly id: string; + readonly name?: string | null; + readonly allSupportedFormFieldsID?: string | null; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; +}; + +export declare type Student = LazyLoading extends LazyLoadingDisabled ? EagerStudent : LazyStudent; + +export declare const Student: (new (init: ModelInit) => Student) & { + copyOf(source: Student, mutator: (draft: MutableModel) => MutableModel | void): Student; +}; + +type EagerCPKStudent = { + readonly [__modelMeta__]: { + identifier: CustomIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly specialStudentId: string; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; +}; + +type LazyCPKStudent = { + readonly [__modelMeta__]: { + identifier: CustomIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly specialStudentId: string; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; +}; + +export declare type CPKStudent = LazyLoading extends LazyLoadingDisabled ? EagerCPKStudent : LazyCPKStudent; + +export declare const CPKStudent: (new (init: ModelInit) => CPKStudent) & { + copyOf(source: CPKStudent, mutator: (draft: MutableModel) => MutableModel | void): CPKStudent; +}; + +type EagerCPKTeacher = { + readonly [__modelMeta__]: { + identifier: CustomIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly specialTeacherId: string; + readonly CPKStudent: CPKStudent; + readonly CPKClasses?: (CPKTeacherCPKClass | null)[] | null; + readonly CPKProjects?: (CPKProject | null)[] | null; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; + readonly cPKTeacherCPKStudentSpecialStudentId: string; +}; + +type LazyCPKTeacher = { + readonly [__modelMeta__]: { + identifier: CustomIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly specialTeacherId: string; + readonly CPKStudent: AsyncItem; + readonly CPKClasses: AsyncCollection; + readonly CPKProjects: AsyncCollection; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; + readonly cPKTeacherCPKStudentSpecialStudentId: string; +}; + +export declare type CPKTeacher = LazyLoading extends LazyLoadingDisabled ? EagerCPKTeacher : LazyCPKTeacher; + +export declare const CPKTeacher: (new (init: ModelInit) => CPKTeacher) & { + copyOf(source: CPKTeacher, mutator: (draft: MutableModel) => MutableModel | void): CPKTeacher; +}; + +type EagerCPKClass = { + readonly [__modelMeta__]: { + identifier: CustomIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly specialClassId: string; + readonly CPKTeachers?: (CPKTeacherCPKClass | null)[] | null; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; +}; + +type LazyCPKClass = { + readonly [__modelMeta__]: { + identifier: CustomIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly specialClassId: string; + readonly CPKTeachers: AsyncCollection; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; +}; + +export declare type CPKClass = LazyLoading extends LazyLoadingDisabled ? EagerCPKClass : LazyCPKClass; + +export declare const CPKClass: (new (init: ModelInit) => CPKClass) & { + copyOf(source: CPKClass, mutator: (draft: MutableModel) => MutableModel | void): CPKClass; +}; + +type EagerCPKProject = { + readonly [__modelMeta__]: { + identifier: CustomIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly specialProjectId: string; + readonly cPKTeacherID?: string | null; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; +}; + +type LazyCPKProject = { + readonly [__modelMeta__]: { + identifier: CustomIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly specialProjectId: string; + readonly cPKTeacherID?: string | null; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; +}; + +export declare type CPKProject = LazyLoading extends LazyLoadingDisabled ? EagerCPKProject : LazyCPKProject; + +export declare const CPKProject: (new (init: ModelInit) => CPKProject) & { + copyOf(source: CPKProject, mutator: (draft: MutableModel) => MutableModel | void): CPKProject; +}; + +type EagerCompositeDog = { + readonly [__modelMeta__]: { + identifier: CompositeIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly name: string; + readonly description: string; + readonly CompositeBowl?: CompositeBowl | null; + readonly CompositeOwner?: CompositeOwner | null; + readonly CompositeToys?: (CompositeToy | null)[] | null; + readonly CompositeVets?: (CompositeDogCompositeVet | null)[] | null; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; + readonly compositeDogCompositeBowlShape?: string | null; + readonly compositeDogCompositeBowlSize?: string | null; + readonly compositeDogCompositeOwnerLastName?: string | null; + readonly compositeDogCompositeOwnerFirstName?: string | null; +}; + +type LazyCompositeDog = { + readonly [__modelMeta__]: { + identifier: CompositeIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly name: string; + readonly description: string; + readonly CompositeBowl: AsyncItem; + readonly CompositeOwner: AsyncItem; + readonly CompositeToys: AsyncCollection; + readonly CompositeVets: AsyncCollection; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; + readonly compositeDogCompositeBowlShape?: string | null; + readonly compositeDogCompositeBowlSize?: string | null; + readonly compositeDogCompositeOwnerLastName?: string | null; + readonly compositeDogCompositeOwnerFirstName?: string | null; +}; + +export declare type CompositeDog = LazyLoading extends LazyLoadingDisabled ? EagerCompositeDog : LazyCompositeDog; + +export declare const CompositeDog: (new (init: ModelInit) => CompositeDog) & { + copyOf( + source: CompositeDog, + mutator: (draft: MutableModel) => MutableModel | void, + ): CompositeDog; +}; + +type EagerCompositeBowl = { + readonly [__modelMeta__]: { + identifier: CompositeIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly shape: string; + readonly size: string; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; +}; + +type LazyCompositeBowl = { + readonly [__modelMeta__]: { + identifier: CompositeIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly shape: string; + readonly size: string; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; +}; + +export declare type CompositeBowl = LazyLoading extends LazyLoadingDisabled ? EagerCompositeBowl : LazyCompositeBowl; + +export declare const CompositeBowl: (new (init: ModelInit) => CompositeBowl) & { + copyOf( + source: CompositeBowl, + mutator: (draft: MutableModel) => MutableModel | void, + ): CompositeBowl; +}; + +type EagerCompositeOwner = { + readonly [__modelMeta__]: { + identifier: CompositeIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly lastName: string; + readonly firstName: string; + readonly CompositeDog?: CompositeDog | null; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; + readonly compositeOwnerCompositeDogName?: string | null; + readonly compositeOwnerCompositeDogDescription?: string | null; +}; + +type LazyCompositeOwner = { + readonly [__modelMeta__]: { + identifier: CompositeIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly lastName: string; + readonly firstName: string; + readonly CompositeDog: AsyncItem; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; + readonly compositeOwnerCompositeDogName?: string | null; + readonly compositeOwnerCompositeDogDescription?: string | null; +}; + +export declare type CompositeOwner = LazyLoading extends LazyLoadingDisabled ? EagerCompositeOwner : LazyCompositeOwner; + +export declare const CompositeOwner: (new (init: ModelInit) => CompositeOwner) & { + copyOf( + source: CompositeOwner, + mutator: (draft: MutableModel) => MutableModel | void, + ): CompositeOwner; +}; + +type EagerCompositeToy = { + readonly [__modelMeta__]: { + identifier: CompositeIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly kind: string; + readonly color: string; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; + readonly compositeDogCompositeToysName?: string | null; + readonly compositeDogCompositeToysDescription?: string | null; +}; + +type LazyCompositeToy = { + readonly [__modelMeta__]: { + identifier: CompositeIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly kind: string; + readonly color: string; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; + readonly compositeDogCompositeToysName?: string | null; + readonly compositeDogCompositeToysDescription?: string | null; +}; + +export declare type CompositeToy = LazyLoading extends LazyLoadingDisabled ? EagerCompositeToy : LazyCompositeToy; + +export declare const CompositeToy: (new (init: ModelInit) => CompositeToy) & { + copyOf( + source: CompositeToy, + mutator: (draft: MutableModel) => MutableModel | void, + ): CompositeToy; +}; + +type EagerCompositeVet = { + readonly [__modelMeta__]: { + identifier: CompositeIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly specialty: string; + readonly city: string; + readonly CompositeDogs?: (CompositeDogCompositeVet | null)[] | null; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; +}; + +type LazyCompositeVet = { + readonly [__modelMeta__]: { + identifier: CompositeIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly specialty: string; + readonly city: string; + readonly CompositeDogs: AsyncCollection; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; +}; + +export declare type CompositeVet = LazyLoading extends LazyLoadingDisabled ? EagerCompositeVet : LazyCompositeVet; + +export declare const CompositeVet: (new (init: ModelInit) => CompositeVet) & { + copyOf( + source: CompositeVet, + mutator: (draft: MutableModel) => MutableModel | void, + ): CompositeVet; +}; + +type EagerBiDirectionalDog = { + readonly [__modelMeta__]: { + identifier: ManagedIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly id: string; + readonly name?: string | null; + readonly BiDirectionalOwner?: BiDirectionalOwner | null; + readonly BiDirectionalToys?: (BiDirectionalToy | null)[] | null; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; + readonly biDirectionalDogBiDirectionalOwnerId?: string | null; +}; + +type LazyBiDirectionalDog = { + readonly [__modelMeta__]: { + identifier: ManagedIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly id: string; + readonly name?: string | null; + readonly BiDirectionalOwner: AsyncItem; + readonly BiDirectionalToys: AsyncCollection; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; + readonly biDirectionalDogBiDirectionalOwnerId?: string | null; +}; + +export declare type BiDirectionalDog = LazyLoading extends LazyLoadingDisabled + ? EagerBiDirectionalDog + : LazyBiDirectionalDog; + +export declare const BiDirectionalDog: (new (init: ModelInit) => BiDirectionalDog) & { + copyOf( + source: BiDirectionalDog, + mutator: (draft: MutableModel) => MutableModel | void, + ): BiDirectionalDog; +}; + +type EagerBiDirectionalOwner = { + readonly [__modelMeta__]: { + identifier: ManagedIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly id: string; + readonly name?: string | null; + readonly biDirectionalDogID: string; + readonly BiDirectionalDog: BiDirectionalDog; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; +}; + +type LazyBiDirectionalOwner = { + readonly [__modelMeta__]: { + identifier: ManagedIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly id: string; + readonly name?: string | null; + readonly biDirectionalDogID: string; + readonly BiDirectionalDog: AsyncItem; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; +}; + +export declare type BiDirectionalOwner = LazyLoading extends LazyLoadingDisabled + ? EagerBiDirectionalOwner + : LazyBiDirectionalOwner; + +export declare const BiDirectionalOwner: (new (init: ModelInit) => BiDirectionalOwner) & { + copyOf( + source: BiDirectionalOwner, + mutator: (draft: MutableModel) => MutableModel | void, + ): BiDirectionalOwner; +}; + +type EagerBiDirectionalToy = { + readonly [__modelMeta__]: { + identifier: ManagedIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly id: string; + readonly name?: string | null; + readonly biDirectionalDogID: string; + readonly BiDirectionalDog: BiDirectionalDog; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; + readonly biDirectionalDogBiDirectionalToysId?: string | null; +}; + +type LazyBiDirectionalToy = { + readonly [__modelMeta__]: { + identifier: ManagedIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly id: string; + readonly name?: string | null; + readonly biDirectionalDogID: string; + readonly BiDirectionalDog: AsyncItem; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; + readonly biDirectionalDogBiDirectionalToysId?: string | null; +}; + +export declare type BiDirectionalToy = LazyLoading extends LazyLoadingDisabled + ? EagerBiDirectionalToy + : LazyBiDirectionalToy; + +export declare const BiDirectionalToy: (new (init: ModelInit) => BiDirectionalToy) & { + copyOf( + source: BiDirectionalToy, + mutator: (draft: MutableModel) => MutableModel | void, + ): BiDirectionalToy; +}; + +type EagerModelWithVariableCollisions = { + readonly [__modelMeta__]: { + identifier: ManagedIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly id: string; + readonly modelWithVariableCollisions?: string | null; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; +}; + +type LazyModelWithVariableCollisions = { + readonly [__modelMeta__]: { + identifier: ManagedIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly id: string; + readonly modelWithVariableCollisions?: string | null; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; +}; + +export declare type ModelWithVariableCollisions = LazyLoading extends LazyLoadingDisabled + ? EagerModelWithVariableCollisions + : LazyModelWithVariableCollisions; + +export declare const ModelWithVariableCollisions: (new ( + init: ModelInit, +) => ModelWithVariableCollisions) & { + copyOf( + source: ModelWithVariableCollisions, + mutator: (draft: MutableModel) => MutableModel | void, + ): ModelWithVariableCollisions; +}; + +type EagerDealership = { + readonly [__modelMeta__]: { + identifier: ManagedIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly id: string; + readonly name: string; + readonly cars?: (Car | null)[] | null; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; +}; + +type LazyDealership = { + readonly [__modelMeta__]: { + identifier: ManagedIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly id: string; + readonly name: string; + readonly cars: AsyncCollection; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; +}; + +export declare type Dealership = LazyLoading extends LazyLoadingDisabled ? EagerDealership : LazyDealership; + +export declare const Dealership: (new (init: ModelInit) => Dealership) & { + copyOf(source: Dealership, mutator: (draft: MutableModel) => MutableModel | void): Dealership; +}; + +type EagerCar = { + readonly [__modelMeta__]: { + identifier: ManagedIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly id: string; + readonly name: string; + readonly dealershipId?: string | null; + readonly dealership?: Dealership | null; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; +}; + +type LazyCar = { + readonly [__modelMeta__]: { + identifier: ManagedIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly id: string; + readonly name: string; + readonly dealershipId?: string | null; + readonly dealership: AsyncItem; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; +}; + +export declare type Car = LazyLoading extends LazyLoadingDisabled ? EagerCar : LazyCar; + +export declare const Car: (new (init: ModelInit) => Car) & { + copyOf(source: Car, mutator: (draft: MutableModel) => MutableModel | void): Car; +}; + +type EagerAllSupportedFormFieldsTag = { + readonly [__modelMeta__]: { + identifier: ManagedIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly id: string; + readonly tagId?: string | null; + readonly allSupportedFormFieldsId?: string | null; + readonly tag: Tag; + readonly allSupportedFormFields: AllSupportedFormFields; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; +}; + +type LazyAllSupportedFormFieldsTag = { + readonly [__modelMeta__]: { + identifier: ManagedIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly id: string; + readonly tagId?: string | null; + readonly allSupportedFormFieldsId?: string | null; + readonly tag: AsyncItem; + readonly allSupportedFormFields: AsyncItem; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; +}; + +export declare type AllSupportedFormFieldsTag = LazyLoading extends LazyLoadingDisabled + ? EagerAllSupportedFormFieldsTag + : LazyAllSupportedFormFieldsTag; + +export declare const AllSupportedFormFieldsTag: (new ( + init: ModelInit, +) => AllSupportedFormFieldsTag) & { + copyOf( + source: AllSupportedFormFieldsTag, + mutator: (draft: MutableModel) => MutableModel | void, + ): AllSupportedFormFieldsTag; +}; + +type EagerCPKTeacherCPKClass = { + readonly [__modelMeta__]: { + identifier: ManagedIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly id: string; + readonly cPKTeacherSpecialTeacherId?: string | null; + readonly cPKClassSpecialClassId?: string | null; + readonly cpkTeacher: CPKTeacher; + readonly cpkClass: CPKClass; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; +}; + +type LazyCPKTeacherCPKClass = { + readonly [__modelMeta__]: { + identifier: ManagedIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly id: string; + readonly cPKTeacherSpecialTeacherId?: string | null; + readonly cPKClassSpecialClassId?: string | null; + readonly cpkTeacher: AsyncItem; + readonly cpkClass: AsyncItem; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; +}; + +export declare type CPKTeacherCPKClass = LazyLoading extends LazyLoadingDisabled + ? EagerCPKTeacherCPKClass + : LazyCPKTeacherCPKClass; + +export declare const CPKTeacherCPKClass: (new (init: ModelInit) => CPKTeacherCPKClass) & { + copyOf( + source: CPKTeacherCPKClass, + mutator: (draft: MutableModel) => MutableModel | void, + ): CPKTeacherCPKClass; +}; + +type EagerCompositeDogCompositeVet = { + readonly [__modelMeta__]: { + identifier: ManagedIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly id: string; + readonly compositeDogName?: string | null; + readonly compositeDogdescription?: string | null; + readonly compositeVetSpecialty?: string | null; + readonly compositeVetcity?: string | null; + readonly compositeDog: CompositeDog; + readonly compositeVet: CompositeVet; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; +}; + +type LazyCompositeDogCompositeVet = { + readonly [__modelMeta__]: { + identifier: ManagedIdentifier; + readOnlyFields: 'createdAt' | 'updatedAt'; + }; + readonly id: string; + readonly compositeDogName?: string | null; + readonly compositeDogdescription?: string | null; + readonly compositeVetSpecialty?: string | null; + readonly compositeVetcity?: string | null; + readonly compositeDog: AsyncItem; + readonly compositeVet: AsyncItem; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; +}; + +export declare type CompositeDogCompositeVet = LazyLoading extends LazyLoadingDisabled + ? EagerCompositeDogCompositeVet + : LazyCompositeDogCompositeVet; + +export declare const CompositeDogCompositeVet: (new ( + init: ModelInit, +) => CompositeDogCompositeVet) & { + copyOf( + source: CompositeDogCompositeVet, + mutator: (draft: MutableModel) => MutableModel | void, + ): CompositeDogCompositeVet; +}; diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/src/models/index.js b/packages/test-generator/integration-test-templates-amplify-js-v6/src/models/index.js new file mode 100644 index 000000000..86d954272 --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/src/models/index.js @@ -0,0 +1,93 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { initSchema } from '@aws-amplify/datastore'; +import { schema } from './schema'; + +const City = { + SAN_FRANCISCO: 'SAN_FRANCISCO', + NEW_YORK: 'NEW_YORK', + HOUSTON: 'HOUSTON', + AUSTIN: 'AUSTIN', + LOS_ANGELES: 'LOS_ANGELES', + CHICAGO: 'CHICAGO', + SAN_DIEGO: 'SAN_DIEGO', + NEW_HAVEN: 'NEW_HAVEN', + PORTLAND: 'PORTLAND', + SEATTLE: 'SEATTLE', +}; + +const { + UserPreference, + User, + Listing, + ComplexModel, + Class, + Tag, + AllSupportedFormFields, + Owner, + Student, + CPKStudent, + CPKTeacher, + CPKClass, + CPKProject, + CompositeDog, + CompositeBowl, + CompositeOwner, + CompositeToy, + CompositeVet, + BiDirectionalDog, + BiDirectionalOwner, + BiDirectionalToy, + ModelWithVariableCollisions, + Dealership, + Car, + AllSupportedFormFieldsTag, + CPKTeacherCPKClass, + CompositeDogCompositeVet, + CustomType, +} = initSchema(schema); + +export { + UserPreference, + User, + Listing, + ComplexModel, + Class, + Tag, + AllSupportedFormFields, + Owner, + Student, + CPKStudent, + CPKTeacher, + CPKClass, + CPKProject, + CompositeDog, + CompositeBowl, + CompositeOwner, + CompositeToy, + CompositeVet, + BiDirectionalDog, + BiDirectionalOwner, + BiDirectionalToy, + ModelWithVariableCollisions, + Dealership, + Car, + AllSupportedFormFieldsTag, + CPKTeacherCPKClass, + CompositeDogCompositeVet, + City, + CustomType, +}; diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/src/models/schema.d.ts b/packages/test-generator/integration-test-templates-amplify-js-v6/src/models/schema.d.ts new file mode 100644 index 000000000..a3a625869 --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/src/models/schema.d.ts @@ -0,0 +1,18 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { Schema } from '@aws-amplify/datastore'; + +export declare const schema: Schema; diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/src/models/schema.js b/packages/test-generator/integration-test-templates-amplify-js-v6/src/models/schema.js new file mode 100644 index 000000000..0f8f6bf1d --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/src/models/schema.js @@ -0,0 +1,2044 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +export const schema = { + models: { + UserPreference: { + name: 'UserPreference', + fields: { + id: { + name: 'id', + isArray: false, + type: 'ID', + isRequired: true, + attributes: [], + }, + favoriteColor: { + name: 'favoriteColor', + isArray: false, + type: 'String', + isRequired: false, + attributes: [], + }, + createdAt: { + name: 'createdAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + updatedAt: { + name: 'updatedAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + }, + syncable: true, + pluralName: 'UserPreferences', + attributes: [ + { + type: 'model', + properties: {}, + }, + ], + }, + User: { + name: 'User', + fields: { + id: { + name: 'id', + isArray: false, + type: 'ID', + isRequired: true, + attributes: [], + }, + firstName: { + name: 'firstName', + isArray: false, + type: 'String', + isRequired: false, + attributes: [], + }, + lastName: { + name: 'lastName', + isArray: false, + type: 'String', + isRequired: false, + attributes: [], + }, + age: { + name: 'age', + isArray: false, + type: 'Int', + isRequired: false, + attributes: [], + }, + isLoggedIn: { + name: 'isLoggedIn', + isArray: false, + type: 'Boolean', + isRequired: false, + attributes: [], + }, + loggedInColor: { + name: 'loggedInColor', + isArray: false, + type: 'String', + isRequired: false, + attributes: [], + }, + loggedOutColor: { + name: 'loggedOutColor', + isArray: false, + type: 'String', + isRequired: false, + attributes: [], + }, + createdAt: { + name: 'createdAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + updatedAt: { + name: 'updatedAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + }, + syncable: true, + pluralName: 'Users', + attributes: [ + { + type: 'model', + properties: {}, + }, + ], + }, + Listing: { + name: 'Listing', + fields: { + id: { + name: 'id', + isArray: false, + type: 'ID', + isRequired: true, + attributes: [], + }, + title: { + name: 'title', + isArray: false, + type: 'String', + isRequired: false, + attributes: [], + }, + priceUSD: { + name: 'priceUSD', + isArray: false, + type: 'Int', + isRequired: false, + attributes: [], + }, + description: { + name: 'description', + isArray: false, + type: 'String', + isRequired: false, + attributes: [], + }, + createdAt: { + name: 'createdAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + updatedAt: { + name: 'updatedAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + }, + syncable: true, + pluralName: 'Listings', + attributes: [ + { + type: 'model', + properties: {}, + }, + ], + }, + ComplexModel: { + name: 'ComplexModel', + fields: { + id: { + name: 'id', + isArray: false, + type: 'ID', + isRequired: true, + attributes: [], + }, + listElement: { + name: 'listElement', + isArray: true, + type: 'String', + isRequired: false, + attributes: [], + isArrayNullable: false, + }, + myCustomField: { + name: 'myCustomField', + isArray: false, + type: { + nonModel: 'CustomType', + }, + isRequired: false, + attributes: [], + }, + createdAt: { + name: 'createdAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + updatedAt: { + name: 'updatedAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + }, + syncable: true, + pluralName: 'ComplexModels', + attributes: [ + { + type: 'model', + properties: {}, + }, + ], + }, + Class: { + name: 'Class', + fields: { + id: { + name: 'id', + isArray: false, + type: 'ID', + isRequired: true, + attributes: [], + }, + name: { + name: 'name', + isArray: false, + type: 'String', + isRequired: false, + attributes: [], + }, + createdAt: { + name: 'createdAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + updatedAt: { + name: 'updatedAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + }, + syncable: true, + pluralName: 'Classes', + attributes: [ + { + type: 'model', + properties: {}, + }, + ], + }, + Tag: { + name: 'Tag', + fields: { + id: { + name: 'id', + isArray: false, + type: 'ID', + isRequired: true, + attributes: [], + }, + label: { + name: 'label', + isArray: false, + type: 'String', + isRequired: false, + attributes: [], + }, + AllSupportedFormFields: { + name: 'AllSupportedFormFields', + isArray: true, + type: { + model: 'AllSupportedFormFieldsTag', + }, + isRequired: false, + attributes: [], + isArrayNullable: true, + association: { + connectionType: 'HAS_MANY', + associatedWith: ['tag'], + }, + }, + createdAt: { + name: 'createdAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + updatedAt: { + name: 'updatedAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + }, + syncable: true, + pluralName: 'Tags', + attributes: [ + { + type: 'model', + properties: {}, + }, + ], + }, + AllSupportedFormFields: { + name: 'AllSupportedFormFields', + fields: { + id: { + name: 'id', + isArray: false, + type: 'ID', + isRequired: true, + attributes: [], + }, + string: { + name: 'string', + isArray: false, + type: 'String', + isRequired: false, + attributes: [], + }, + stringArray: { + name: 'stringArray', + isArray: true, + type: 'String', + isRequired: false, + attributes: [], + isArrayNullable: true, + }, + int: { + name: 'int', + isArray: false, + type: 'Int', + isRequired: false, + attributes: [], + }, + float: { + name: 'float', + isArray: false, + type: 'Float', + isRequired: false, + attributes: [], + }, + awsDate: { + name: 'awsDate', + isArray: false, + type: 'AWSDate', + isRequired: false, + attributes: [], + }, + awsTime: { + name: 'awsTime', + isArray: false, + type: 'AWSTime', + isRequired: false, + attributes: [], + }, + awsDateTime: { + name: 'awsDateTime', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + }, + awsTimestamp: { + name: 'awsTimestamp', + isArray: false, + type: 'AWSTimestamp', + isRequired: false, + attributes: [], + }, + awsEmail: { + name: 'awsEmail', + isArray: false, + type: 'AWSEmail', + isRequired: false, + attributes: [], + }, + awsUrl: { + name: 'awsUrl', + isArray: false, + type: 'AWSURL', + isRequired: false, + attributes: [], + }, + awsIPAddress: { + name: 'awsIPAddress', + isArray: false, + type: 'AWSIPAddress', + isRequired: false, + attributes: [], + }, + boolean: { + name: 'boolean', + isArray: false, + type: 'Boolean', + isRequired: false, + attributes: [], + }, + awsJson: { + name: 'awsJson', + isArray: false, + type: 'AWSJSON', + isRequired: false, + attributes: [], + }, + awsPhone: { + name: 'awsPhone', + isArray: false, + type: 'AWSPhone', + isRequired: false, + attributes: [], + }, + enum: { + name: 'enum', + isArray: false, + type: { + enum: 'City', + }, + isRequired: false, + attributes: [], + }, + nonModelField: { + name: 'nonModelField', + isArray: false, + type: { + nonModel: 'CustomType', + }, + isRequired: false, + attributes: [], + }, + nonModelFieldArray: { + name: 'nonModelFieldArray', + isArray: true, + type: { + nonModel: 'CustomType', + }, + isRequired: false, + attributes: [], + isArrayNullable: true, + }, + HasOneUser: { + name: 'HasOneUser', + isArray: false, + type: { + model: 'User', + }, + isRequired: false, + attributes: [], + association: { + connectionType: 'HAS_ONE', + associatedWith: ['id'], + targetNames: ['allSupportedFormFieldsHasOneUserId'], + }, + }, + BelongsToOwner: { + name: 'BelongsToOwner', + isArray: false, + type: { + model: 'Owner', + }, + isRequired: false, + attributes: [], + association: { + connectionType: 'BELONGS_TO', + targetNames: ['allSupportedFormFieldsBelongsToOwnerId'], + }, + }, + HasManyStudents: { + name: 'HasManyStudents', + isArray: true, + type: { + model: 'Student', + }, + isRequired: false, + attributes: [], + isArrayNullable: true, + association: { + connectionType: 'HAS_MANY', + associatedWith: ['allSupportedFormFieldsID'], + }, + }, + ManyToManyTags: { + name: 'ManyToManyTags', + isArray: true, + type: { + model: 'AllSupportedFormFieldsTag', + }, + isRequired: false, + attributes: [], + isArrayNullable: true, + association: { + connectionType: 'HAS_MANY', + associatedWith: ['allSupportedFormFields'], + }, + }, + createdAt: { + name: 'createdAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + updatedAt: { + name: 'updatedAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + allSupportedFormFieldsHasOneUserId: { + name: 'allSupportedFormFieldsHasOneUserId', + isArray: false, + type: 'ID', + isRequired: false, + attributes: [], + }, + allSupportedFormFieldsBelongsToOwnerId: { + name: 'allSupportedFormFieldsBelongsToOwnerId', + isArray: false, + type: 'ID', + isRequired: false, + attributes: [], + }, + }, + syncable: true, + pluralName: 'AllSupportedFormFields', + attributes: [ + { + type: 'model', + properties: {}, + }, + ], + }, + Owner: { + name: 'Owner', + fields: { + id: { + name: 'id', + isArray: false, + type: 'ID', + isRequired: true, + attributes: [], + }, + name: { + name: 'name', + isArray: false, + type: 'String', + isRequired: false, + attributes: [], + }, + AllSupportedFormFields: { + name: 'AllSupportedFormFields', + isArray: false, + type: { + model: 'AllSupportedFormFields', + }, + isRequired: false, + attributes: [], + association: { + connectionType: 'HAS_ONE', + associatedWith: ['id'], + targetNames: ['ownerAllSupportedFormFieldsId'], + }, + }, + createdAt: { + name: 'createdAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + updatedAt: { + name: 'updatedAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + ownerAllSupportedFormFieldsId: { + name: 'ownerAllSupportedFormFieldsId', + isArray: false, + type: 'ID', + isRequired: false, + attributes: [], + }, + }, + syncable: true, + pluralName: 'Owners', + attributes: [ + { + type: 'model', + properties: {}, + }, + ], + }, + Student: { + name: 'Student', + fields: { + id: { + name: 'id', + isArray: false, + type: 'ID', + isRequired: true, + attributes: [], + }, + name: { + name: 'name', + isArray: false, + type: 'String', + isRequired: false, + attributes: [], + }, + allSupportedFormFieldsID: { + name: 'allSupportedFormFieldsID', + isArray: false, + type: 'ID', + isRequired: false, + attributes: [], + }, + createdAt: { + name: 'createdAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + updatedAt: { + name: 'updatedAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + }, + syncable: true, + pluralName: 'Students', + attributes: [ + { + type: 'model', + properties: {}, + }, + { + type: 'key', + properties: { + name: 'byAllSupportedFormFields', + fields: ['allSupportedFormFieldsID'], + }, + }, + ], + }, + CPKStudent: { + name: 'CPKStudent', + fields: { + specialStudentId: { + name: 'specialStudentId', + isArray: false, + type: 'ID', + isRequired: true, + attributes: [], + }, + createdAt: { + name: 'createdAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + updatedAt: { + name: 'updatedAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + }, + syncable: true, + pluralName: 'CPKStudents', + attributes: [ + { + type: 'model', + properties: {}, + }, + { + type: 'key', + properties: { + fields: ['specialStudentId'], + }, + }, + ], + }, + CPKTeacher: { + name: 'CPKTeacher', + fields: { + specialTeacherId: { + name: 'specialTeacherId', + isArray: false, + type: 'ID', + isRequired: true, + attributes: [], + }, + CPKStudent: { + name: 'CPKStudent', + isArray: false, + type: { + model: 'CPKStudent', + }, + isRequired: true, + attributes: [], + association: { + connectionType: 'HAS_ONE', + associatedWith: ['specialStudentId'], + targetNames: ['cPKTeacherCPKStudentSpecialStudentId'], + }, + }, + CPKClasses: { + name: 'CPKClasses', + isArray: true, + type: { + model: 'CPKTeacherCPKClass', + }, + isRequired: false, + attributes: [], + isArrayNullable: true, + association: { + connectionType: 'HAS_MANY', + associatedWith: ['cpkTeacher'], + }, + }, + CPKProjects: { + name: 'CPKProjects', + isArray: true, + type: { + model: 'CPKProject', + }, + isRequired: false, + attributes: [], + isArrayNullable: true, + association: { + connectionType: 'HAS_MANY', + associatedWith: ['cPKTeacherID'], + }, + }, + createdAt: { + name: 'createdAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + updatedAt: { + name: 'updatedAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + cPKTeacherCPKStudentSpecialStudentId: { + name: 'cPKTeacherCPKStudentSpecialStudentId', + isArray: false, + type: 'ID', + isRequired: true, + attributes: [], + }, + }, + syncable: true, + pluralName: 'CPKTeachers', + attributes: [ + { + type: 'model', + properties: {}, + }, + { + type: 'key', + properties: { + fields: ['specialTeacherId'], + }, + }, + ], + }, + CPKClass: { + name: 'CPKClass', + fields: { + specialClassId: { + name: 'specialClassId', + isArray: false, + type: 'ID', + isRequired: true, + attributes: [], + }, + CPKTeachers: { + name: 'CPKTeachers', + isArray: true, + type: { + model: 'CPKTeacherCPKClass', + }, + isRequired: false, + attributes: [], + isArrayNullable: true, + association: { + connectionType: 'HAS_MANY', + associatedWith: ['cpkClass'], + }, + }, + createdAt: { + name: 'createdAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + updatedAt: { + name: 'updatedAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + }, + syncable: true, + pluralName: 'CPKClasses', + attributes: [ + { + type: 'model', + properties: {}, + }, + { + type: 'key', + properties: { + fields: ['specialClassId'], + }, + }, + ], + }, + CPKProject: { + name: 'CPKProject', + fields: { + specialProjectId: { + name: 'specialProjectId', + isArray: false, + type: 'ID', + isRequired: true, + attributes: [], + }, + cPKTeacherID: { + name: 'cPKTeacherID', + isArray: false, + type: 'ID', + isRequired: false, + attributes: [], + }, + createdAt: { + name: 'createdAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + updatedAt: { + name: 'updatedAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + }, + syncable: true, + pluralName: 'CPKProjects', + attributes: [ + { + type: 'model', + properties: {}, + }, + { + type: 'key', + properties: { + fields: ['specialProjectId'], + }, + }, + { + type: 'key', + properties: { + name: 'byCPKTeacher', + fields: ['cPKTeacherID'], + }, + }, + ], + }, + CompositeDog: { + name: 'CompositeDog', + fields: { + name: { + name: 'name', + isArray: false, + type: 'ID', + isRequired: true, + attributes: [], + }, + description: { + name: 'description', + isArray: false, + type: 'String', + isRequired: true, + attributes: [], + }, + CompositeBowl: { + name: 'CompositeBowl', + isArray: false, + type: { + model: 'CompositeBowl', + }, + isRequired: false, + attributes: [], + association: { + connectionType: 'HAS_ONE', + associatedWith: ['shape', 'size'], + targetNames: ['compositeDogCompositeBowlShape', 'compositeDogCompositeBowlSize'], + }, + }, + CompositeOwner: { + name: 'CompositeOwner', + isArray: false, + type: { + model: 'CompositeOwner', + }, + isRequired: false, + attributes: [], + association: { + connectionType: 'BELONGS_TO', + targetNames: ['compositeDogCompositeOwnerLastName', 'compositeDogCompositeOwnerFirstName'], + }, + }, + CompositeToys: { + name: 'CompositeToys', + isArray: true, + type: { + model: 'CompositeToy', + }, + isRequired: false, + attributes: [], + isArrayNullable: true, + association: { + connectionType: 'HAS_MANY', + associatedWith: ['compositeDogCompositeToysName', 'compositeDogCompositeToysDescription'], + }, + }, + CompositeVets: { + name: 'CompositeVets', + isArray: true, + type: { + model: 'CompositeDogCompositeVet', + }, + isRequired: false, + attributes: [], + isArrayNullable: true, + association: { + connectionType: 'HAS_MANY', + associatedWith: ['compositeDog'], + }, + }, + createdAt: { + name: 'createdAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + updatedAt: { + name: 'updatedAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + compositeDogCompositeBowlShape: { + name: 'compositeDogCompositeBowlShape', + isArray: false, + type: 'ID', + isRequired: false, + attributes: [], + }, + compositeDogCompositeBowlSize: { + name: 'compositeDogCompositeBowlSize', + isArray: false, + type: 'String', + isRequired: false, + attributes: [], + }, + compositeDogCompositeOwnerLastName: { + name: 'compositeDogCompositeOwnerLastName', + isArray: false, + type: 'ID', + isRequired: false, + attributes: [], + }, + compositeDogCompositeOwnerFirstName: { + name: 'compositeDogCompositeOwnerFirstName', + isArray: false, + type: 'String', + isRequired: false, + attributes: [], + }, + }, + syncable: true, + pluralName: 'CompositeDogs', + attributes: [ + { + type: 'model', + properties: {}, + }, + { + type: 'key', + properties: { + fields: ['name', 'description'], + }, + }, + ], + }, + CompositeBowl: { + name: 'CompositeBowl', + fields: { + shape: { + name: 'shape', + isArray: false, + type: 'ID', + isRequired: true, + attributes: [], + }, + size: { + name: 'size', + isArray: false, + type: 'String', + isRequired: true, + attributes: [], + }, + createdAt: { + name: 'createdAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + updatedAt: { + name: 'updatedAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + }, + syncable: true, + pluralName: 'CompositeBowls', + attributes: [ + { + type: 'model', + properties: {}, + }, + { + type: 'key', + properties: { + fields: ['shape', 'size'], + }, + }, + ], + }, + CompositeOwner: { + name: 'CompositeOwner', + fields: { + lastName: { + name: 'lastName', + isArray: false, + type: 'ID', + isRequired: true, + attributes: [], + }, + firstName: { + name: 'firstName', + isArray: false, + type: 'String', + isRequired: true, + attributes: [], + }, + CompositeDog: { + name: 'CompositeDog', + isArray: false, + type: { + model: 'CompositeDog', + }, + isRequired: false, + attributes: [], + association: { + connectionType: 'HAS_ONE', + associatedWith: ['name', 'description'], + targetNames: ['compositeOwnerCompositeDogName', 'compositeOwnerCompositeDogDescription'], + }, + }, + createdAt: { + name: 'createdAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + updatedAt: { + name: 'updatedAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + compositeOwnerCompositeDogName: { + name: 'compositeOwnerCompositeDogName', + isArray: false, + type: 'ID', + isRequired: false, + attributes: [], + }, + compositeOwnerCompositeDogDescription: { + name: 'compositeOwnerCompositeDogDescription', + isArray: false, + type: 'String', + isRequired: false, + attributes: [], + }, + }, + syncable: true, + pluralName: 'CompositeOwners', + attributes: [ + { + type: 'model', + properties: {}, + }, + { + type: 'key', + properties: { + fields: ['lastName', 'firstName'], + }, + }, + ], + }, + CompositeToy: { + name: 'CompositeToy', + fields: { + kind: { + name: 'kind', + isArray: false, + type: 'ID', + isRequired: true, + attributes: [], + }, + color: { + name: 'color', + isArray: false, + type: 'String', + isRequired: true, + attributes: [], + }, + createdAt: { + name: 'createdAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + updatedAt: { + name: 'updatedAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + compositeDogCompositeToysName: { + name: 'compositeDogCompositeToysName', + isArray: false, + type: 'ID', + isRequired: false, + attributes: [], + }, + compositeDogCompositeToysDescription: { + name: 'compositeDogCompositeToysDescription', + isArray: false, + type: 'String', + isRequired: false, + attributes: [], + }, + }, + syncable: true, + pluralName: 'CompositeToys', + attributes: [ + { + type: 'model', + properties: {}, + }, + { + type: 'key', + properties: { + fields: ['kind', 'color'], + }, + }, + { + type: 'key', + properties: { + name: 'gsi-CompositeDog.CompositeToys', + fields: ['compositeDogCompositeToysName', 'compositeDogCompositeToysDescription'], + }, + }, + ], + }, + CompositeVet: { + name: 'CompositeVet', + fields: { + specialty: { + name: 'specialty', + isArray: false, + type: 'ID', + isRequired: true, + attributes: [], + }, + city: { + name: 'city', + isArray: false, + type: 'String', + isRequired: true, + attributes: [], + }, + CompositeDogs: { + name: 'CompositeDogs', + isArray: true, + type: { + model: 'CompositeDogCompositeVet', + }, + isRequired: false, + attributes: [], + isArrayNullable: true, + association: { + connectionType: 'HAS_MANY', + associatedWith: ['compositeVet'], + }, + }, + createdAt: { + name: 'createdAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + updatedAt: { + name: 'updatedAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + }, + syncable: true, + pluralName: 'CompositeVets', + attributes: [ + { + type: 'model', + properties: {}, + }, + { + type: 'key', + properties: { + fields: ['specialty', 'city'], + }, + }, + ], + }, + BiDirectionalDog: { + name: 'BiDirectionalDog', + fields: { + id: { + name: 'id', + isArray: false, + type: 'ID', + isRequired: true, + attributes: [], + }, + name: { + name: 'name', + isArray: false, + type: 'String', + isRequired: false, + attributes: [], + }, + BiDirectionalOwner: { + name: 'BiDirectionalOwner', + isArray: false, + type: { + model: 'BiDirectionalOwner', + }, + isRequired: false, + attributes: [], + association: { + connectionType: 'BELONGS_TO', + targetNames: ['biDirectionalDogBiDirectionalOwnerId'], + }, + }, + BiDirectionalToys: { + name: 'BiDirectionalToys', + isArray: true, + type: { + model: 'BiDirectionalToy', + }, + isRequired: false, + attributes: [], + isArrayNullable: true, + association: { + connectionType: 'HAS_MANY', + associatedWith: ['biDirectionalDogBiDirectionalToysId'], + }, + }, + createdAt: { + name: 'createdAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + updatedAt: { + name: 'updatedAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + biDirectionalDogBiDirectionalOwnerId: { + name: 'biDirectionalDogBiDirectionalOwnerId', + isArray: false, + type: 'ID', + isRequired: false, + attributes: [], + }, + }, + syncable: true, + pluralName: 'BiDirectionalDogs', + attributes: [ + { + type: 'model', + properties: {}, + }, + ], + }, + BiDirectionalOwner: { + name: 'BiDirectionalOwner', + fields: { + id: { + name: 'id', + isArray: false, + type: 'ID', + isRequired: true, + attributes: [], + }, + name: { + name: 'name', + isArray: false, + type: 'String', + isRequired: false, + attributes: [], + }, + biDirectionalDogID: { + name: 'biDirectionalDogID', + isArray: false, + type: 'ID', + isRequired: true, + attributes: [], + }, + BiDirectionalDog: { + name: 'BiDirectionalDog', + isArray: false, + type: { + model: 'BiDirectionalDog', + }, + isRequired: true, + attributes: [], + association: { + connectionType: 'HAS_ONE', + associatedWith: ['id'], + targetNames: ['biDirectionalDogID'], + }, + }, + createdAt: { + name: 'createdAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + updatedAt: { + name: 'updatedAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + }, + syncable: true, + pluralName: 'BiDirectionalOwners', + attributes: [ + { + type: 'model', + properties: {}, + }, + ], + }, + BiDirectionalToy: { + name: 'BiDirectionalToy', + fields: { + id: { + name: 'id', + isArray: false, + type: 'ID', + isRequired: true, + attributes: [], + }, + name: { + name: 'name', + isArray: false, + type: 'String', + isRequired: false, + attributes: [], + }, + biDirectionalDogID: { + name: 'biDirectionalDogID', + isArray: false, + type: 'ID', + isRequired: true, + attributes: [], + }, + BiDirectionalDog: { + name: 'BiDirectionalDog', + isArray: false, + type: { + model: 'BiDirectionalDog', + }, + isRequired: true, + attributes: [], + association: { + connectionType: 'BELONGS_TO', + targetNames: ['biDirectionalDogID'], + }, + }, + createdAt: { + name: 'createdAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + updatedAt: { + name: 'updatedAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + biDirectionalDogBiDirectionalToysId: { + name: 'biDirectionalDogBiDirectionalToysId', + isArray: false, + type: 'ID', + isRequired: false, + attributes: [], + }, + }, + syncable: true, + pluralName: 'BiDirectionalToys', + attributes: [ + { + type: 'model', + properties: {}, + }, + ], + }, + ModelWithVariableCollisions: { + name: 'ModelWithVariableCollisions', + fields: { + id: { + name: 'id', + isArray: false, + type: 'ID', + isRequired: true, + attributes: [], + }, + modelWithVariableCollisions: { + name: 'modelWithVariableCollisions', + isArray: false, + type: 'String', + isRequired: false, + attributes: [], + }, + createdAt: { + name: 'createdAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + updatedAt: { + name: 'updatedAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + }, + syncable: true, + pluralName: 'ModelWithVariableCollisions', + attributes: [ + { + type: 'model', + properties: {}, + }, + ], + }, + Dealership: { + name: 'Dealership', + fields: { + id: { + name: 'id', + isArray: false, + type: 'ID', + isRequired: true, + attributes: [], + }, + name: { + name: 'name', + isArray: false, + type: 'String', + isRequired: true, + attributes: [], + }, + cars: { + name: 'cars', + isArray: true, + type: { + model: 'Car', + }, + isRequired: false, + attributes: [], + isArrayNullable: true, + association: { + connectionType: 'HAS_MANY', + associatedWith: ['dealership'], + }, + }, + createdAt: { + name: 'createdAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + updatedAt: { + name: 'updatedAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + }, + syncable: true, + pluralName: 'Dealerships', + attributes: [ + { + type: 'model', + properties: {}, + }, + ], + }, + Car: { + name: 'Car', + fields: { + id: { + name: 'id', + isArray: false, + type: 'ID', + isRequired: true, + attributes: [], + }, + name: { + name: 'name', + isArray: false, + type: 'String', + isRequired: true, + attributes: [], + }, + dealershipId: { + name: 'dealershipId', + isArray: false, + type: 'ID', + isRequired: false, + attributes: [], + }, + dealership: { + name: 'dealership', + isArray: false, + type: { + model: 'Dealership', + }, + isRequired: false, + attributes: [], + association: { + connectionType: 'BELONGS_TO', + targetNames: ['dealershipId'], + }, + }, + createdAt: { + name: 'createdAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + updatedAt: { + name: 'updatedAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + }, + syncable: true, + pluralName: 'Cars', + attributes: [ + { + type: 'model', + properties: {}, + }, + ], + }, + AllSupportedFormFieldsTag: { + name: 'AllSupportedFormFieldsTag', + fields: { + id: { + name: 'id', + isArray: false, + type: 'ID', + isRequired: true, + attributes: [], + }, + tagId: { + name: 'tagId', + isArray: false, + type: 'ID', + isRequired: false, + attributes: [], + }, + allSupportedFormFieldsId: { + name: 'allSupportedFormFieldsId', + isArray: false, + type: 'ID', + isRequired: false, + attributes: [], + }, + tag: { + name: 'tag', + isArray: false, + type: { + model: 'Tag', + }, + isRequired: true, + attributes: [], + association: { + connectionType: 'BELONGS_TO', + targetNames: ['tagId'], + }, + }, + allSupportedFormFields: { + name: 'allSupportedFormFields', + isArray: false, + type: { + model: 'AllSupportedFormFields', + }, + isRequired: true, + attributes: [], + association: { + connectionType: 'BELONGS_TO', + targetNames: ['allSupportedFormFieldsId'], + }, + }, + createdAt: { + name: 'createdAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + updatedAt: { + name: 'updatedAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + }, + syncable: true, + pluralName: 'AllSupportedFormFieldsTags', + attributes: [ + { + type: 'model', + properties: {}, + }, + { + type: 'key', + properties: { + name: 'byTag', + fields: ['tagId'], + }, + }, + { + type: 'key', + properties: { + name: 'byAllSupportedFormFields', + fields: ['allSupportedFormFieldsId'], + }, + }, + ], + }, + CPKTeacherCPKClass: { + name: 'CPKTeacherCPKClass', + fields: { + id: { + name: 'id', + isArray: false, + type: 'ID', + isRequired: true, + attributes: [], + }, + cPKTeacherSpecialTeacherId: { + name: 'cPKTeacherSpecialTeacherId', + isArray: false, + type: 'ID', + isRequired: false, + attributes: [], + }, + cPKClassSpecialClassId: { + name: 'cPKClassSpecialClassId', + isArray: false, + type: 'ID', + isRequired: false, + attributes: [], + }, + cpkTeacher: { + name: 'cpkTeacher', + isArray: false, + type: { + model: 'CPKTeacher', + }, + isRequired: true, + attributes: [], + association: { + connectionType: 'BELONGS_TO', + targetNames: ['cPKTeacherSpecialTeacherId'], + }, + }, + cpkClass: { + name: 'cpkClass', + isArray: false, + type: { + model: 'CPKClass', + }, + isRequired: true, + attributes: [], + association: { + connectionType: 'BELONGS_TO', + targetNames: ['cPKClassSpecialClassId'], + }, + }, + createdAt: { + name: 'createdAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + updatedAt: { + name: 'updatedAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + }, + syncable: true, + pluralName: 'CPKTeacherCPKClasses', + attributes: [ + { + type: 'model', + properties: {}, + }, + { + type: 'key', + properties: { + name: 'byCPKTeacher', + fields: ['cPKTeacherSpecialTeacherId'], + }, + }, + { + type: 'key', + properties: { + name: 'byCPKClass', + fields: ['cPKClassSpecialClassId'], + }, + }, + ], + }, + CompositeDogCompositeVet: { + name: 'CompositeDogCompositeVet', + fields: { + id: { + name: 'id', + isArray: false, + type: 'ID', + isRequired: true, + attributes: [], + }, + compositeDogName: { + name: 'compositeDogName', + isArray: false, + type: 'ID', + isRequired: false, + attributes: [], + }, + compositeDogdescription: { + name: 'compositeDogdescription', + isArray: false, + type: 'String', + isRequired: false, + attributes: [], + }, + compositeVetSpecialty: { + name: 'compositeVetSpecialty', + isArray: false, + type: 'ID', + isRequired: false, + attributes: [], + }, + compositeVetcity: { + name: 'compositeVetcity', + isArray: false, + type: 'String', + isRequired: false, + attributes: [], + }, + compositeDog: { + name: 'compositeDog', + isArray: false, + type: { + model: 'CompositeDog', + }, + isRequired: true, + attributes: [], + association: { + connectionType: 'BELONGS_TO', + targetNames: ['compositeDogName', 'compositeDogdescription'], + }, + }, + compositeVet: { + name: 'compositeVet', + isArray: false, + type: { + model: 'CompositeVet', + }, + isRequired: true, + attributes: [], + association: { + connectionType: 'BELONGS_TO', + targetNames: ['compositeVetSpecialty', 'compositeVetcity'], + }, + }, + createdAt: { + name: 'createdAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + updatedAt: { + name: 'updatedAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + }, + syncable: true, + pluralName: 'CompositeDogCompositeVets', + attributes: [ + { + type: 'model', + properties: {}, + }, + { + type: 'key', + properties: { + name: 'byCompositeDog', + fields: ['compositeDogName', 'compositeDogdescription'], + }, + }, + { + type: 'key', + properties: { + name: 'byCompositeVet', + fields: ['compositeVetSpecialty', 'compositeVetcity'], + }, + }, + ], + }, + }, + enums: { + City: { + name: 'City', + values: [ + 'SAN_FRANCISCO', + 'NEW_YORK', + 'HOUSTON', + 'AUSTIN', + 'LOS_ANGELES', + 'CHICAGO', + 'SAN_DIEGO', + 'NEW_HAVEN', + 'PORTLAND', + 'SEATTLE', + ], + }, + }, + nonModels: { + CustomType: { + name: 'CustomType', + fields: { + StringVal: { + name: 'StringVal', + isArray: false, + type: 'String', + isRequired: false, + attributes: [], + }, + NumVal: { + name: 'NumVal', + isArray: false, + type: 'Int', + isRequired: false, + attributes: [], + }, + BoolVal: { + name: 'BoolVal', + isArray: false, + type: 'Boolean', + isRequired: false, + attributes: [], + }, + }, + }, + }, + codegenVersion: '3.3.6', + version: '832519d29b9b70a1444d1c99127dbd59', +}; diff --git a/packages/test-generator/integration-test-templates-amplify-js-v6/src/test-utils.ts b/packages/test-generator/integration-test-templates-amplify-js-v6/src/test-utils.ts new file mode 100644 index 000000000..7f88cc313 --- /dev/null +++ b/packages/test-generator/integration-test-templates-amplify-js-v6/src/test-utils.ts @@ -0,0 +1,32 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import { AsyncCollection, AsyncItem } from '@aws-amplify/datastore'; + +export async function getModelsFromJoinTableRecords( + record: Record, + fieldName: keyof Record, + dataField: keyof JoinTable, +): Promise { + const joinTableRecords = await (record[fieldName] as unknown as AsyncCollection)?.toArray(); + + return Promise.all( + joinTableRecords.reduce((promises: AsyncItem[], joinTableRecord) => { + promises.push(joinTableRecord[dataField] as unknown as AsyncItem); + return promises; + }, []), + ); +} diff --git a/packages/test-generator/lib/components/primitives/ExpanderPrimitive.json b/packages/test-generator/lib/components/primitives/ExpanderPrimitive.json deleted file mode 100644 index 17113a807..000000000 --- a/packages/test-generator/lib/components/primitives/ExpanderPrimitive.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "id": "1234-5678-9010", - "componentType": "Expander", - "name": "ExpanderPrimitive", - "properties": { - "type": { - "value": "single", - "type": "String" - }, - "isCollapsible": { - "value": true, - "type": "Boolean" - } - }, - "children": [ - { - "componentType": "ExpanderItem", - "name": "ExpanderItem1", - "properties": { - "title": { - "value": "title1" - }, - "value": { - "value": "ExpanderItem1" - }, - "children": { - "value": "ExpanderItem1Content" - } - } - }, - { - "componentType": "ExpanderItem", - "name": "ExpanderItem2", - "properties": { - "title": { - "value": "title2" - }, - "value": { - "value": "ExpanderItem2" - }, - "children": { - "value": "ExpanderItem2Content" - } - } - } - ], - "schemaVersion": "1.0" -} diff --git a/packages/test-generator/lib/components/primitives/TabsPrimitive.json b/packages/test-generator/lib/components/primitives/TabsPrimitive.json deleted file mode 100644 index 086adce25..000000000 --- a/packages/test-generator/lib/components/primitives/TabsPrimitive.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "id": "1234-5678-9010", - "componentType": "Tabs", - "name": "TabsPrimitive", - "properties": {}, - "children": [ - { - "componentType": "TabItem", - "name": "TabItem1", - "properties": { - "title": { - "value": "Tab 1" - }, - "children": { - "value": "Tab 1 Content" - } - } - }, - { - "componentType": "TabItem", - "name": "TabItem2", - "properties": { - "title": { - "value": "Tab 2" - }, - "children": { - "value": "Tab 2 Content" - } - } - } - ], - "schemaVersion": "1.0" -} diff --git a/packages/test-generator/lib/components/primitives/index.ts b/packages/test-generator/lib/components/primitives/index.ts index 20f4d6da6..2a56e7e3b 100644 --- a/packages/test-generator/lib/components/primitives/index.ts +++ b/packages/test-generator/lib/components/primitives/index.ts @@ -21,7 +21,6 @@ export { default as CardPrimitive } from './CardPrimitive.json'; export { default as CheckboxFieldPrimitive } from './CheckboxFieldPrimitive.json'; export { default as CollectionPrimitive } from './CollectionPrimitive.json'; export { default as DividerPrimitive } from './DividerPrimitive.json'; -export { default as ExpanderPrimitive } from './ExpanderPrimitive.json'; export { default as FlexPrimitive } from './FlexPrimitive.json'; export { default as GridPrimitive } from './GridPrimitive.json'; export { default as HeadingPrimitive } from './HeadingPrimitive.json'; @@ -44,7 +43,6 @@ export { default as SelectFieldPrimitive } from './SelectFieldPrimitive.json'; export { default as SliderFieldPrimitive } from './SliderFieldPrimitive.json'; export { default as StepperFieldPrimitive } from './StepperFieldPrimitive.json'; export { default as SwitchFieldPrimitive } from './SwitchFieldPrimitive.json'; -export { default as TabsPrimitive } from './TabsPrimitive.json'; export { default as TablePrimitive } from './TablePrimitive.json'; export { default as TextPrimitive } from './TextPrimitive.json'; export { default as TextAreaFieldPrimitive } from './TextAreaFieldPrimitive.json'; diff --git a/packages/test-generator/lib/generators/TestGenerator.ts b/packages/test-generator/lib/generators/TestGenerator.ts index 0feac36d7..afb81520e 100644 --- a/packages/test-generator/lib/generators/TestGenerator.ts +++ b/packages/test-generator/lib/generators/TestGenerator.ts @@ -26,7 +26,6 @@ import log from 'loglevel'; import * as ComponentSchemas from '../components'; import * as ThemeSchemas from '../themes'; import * as FormSchemas from '../forms'; -import * as ViewSchemas from '../views'; const DEFAULT_RENDER_CONFIG: ReactRenderConfig = { module: ModuleKind.CommonJS, @@ -156,30 +155,6 @@ export abstract class TestGenerator { } }; - const generateView = (testCase: TestCase) => { - const { name, schema } = testCase; - try { - if (this.params.writeToDisk) { - this.writeViewToDisk(schema as StudioView); - } - - if (this.params.writeToLogger) { - const { importsText, compText } = this.renderView(schema as StudioView); - log.info(`# ${name}`); - log.info('## View Only Output'); - log.info('### viewImports'); - log.info(this.decorateTypescriptWithMarkdown(importsText)); - log.info('### viewText'); - log.info(this.decorateTypescriptWithMarkdown(compText)); - } - } catch (err) { - if (this.params.immediatelyThrowGenerateErrors) { - throw err; - } - renderErrors[name] = err; - } - }; - const generateIndexFile = (indexFileTestCases: TestCase[]) => { const schemas = indexFileTestCases.map((testCase) => testCase.schema); try { @@ -254,9 +229,6 @@ export abstract class TestGenerator { case 'Snippet': generateSnippet([testCase]); break; - case 'View': - generateView(testCase); - break; default: throw new Error('Expected either a `Component`, `Theme`, `Form` test case type'); } @@ -323,9 +295,6 @@ export abstract class TestGenerator { ...Object.entries(FormSchemas).map(([name, schema]) => { return { name, schema, testType: 'Form' } as TestCase; }), - ...Object.entries(ViewSchemas).map(([name, schema]) => { - return { name, schema, testType: 'View' } as TestCase; - }), ].filter((testCase) => !disabledSchemaSet.has(testCase.name)); } } diff --git a/packages/test-generator/lib/index.ts b/packages/test-generator/lib/index.ts index a1bdc4c62..28c71e4b9 100644 --- a/packages/test-generator/lib/index.ts +++ b/packages/test-generator/lib/index.ts @@ -17,4 +17,3 @@ export * from './components'; export * from './generators'; export * from './themes'; export * from './forms'; -export * from './views'; diff --git a/packages/test-generator/lib/views/listing-expander-with-component-slot.json b/packages/test-generator/lib/views/listing-expander-with-component-slot.json deleted file mode 100644 index cedd9ca53..000000000 --- a/packages/test-generator/lib/views/listing-expander-with-component-slot.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "appId": "d1234", - "dataSource": { - "model": "Listing", - "sort": [ - { - "field": "title", - "direction": "DESC" - } - ], - "type": "DataStore" - }, - "environmentName": "staging", - "id": "v-123456", - "name": "ListingExpanderWithComponentSlot", - "schemaVersion": "1.0", - "style": {}, - "viewConfiguration": { - "type": "Collection", - "collection": { - "collectionType": "expander", - "title": { - "type": "Amplify.Binding", - "content": { - "bindingProperty": { - "property": "item", - "field": "title" - } - } - }, - "body": { - "type": "Amplify.ComponentSlot", - "content": { - "componentSlot": { - "componentName": "Button", - "bindingProperties": { - "children": { - "property": "item", - "field": "description" - } - } - } - } - } - } - } -} \ No newline at end of file diff --git a/scripts/integ-setupv6.bat b/scripts/integ-setupv6.bat new file mode 100644 index 000000000..d58e74503 --- /dev/null +++ b/scripts/integ-setupv6.bat @@ -0,0 +1,32 @@ +:: clean workspace and build +call npm run integ:clean +call npm run build + +:: create +chdir packages +call npx -y create-react-app integration-test --use-npm --template typescript +chdir .. + +:: add files +call npm run integ:templates + +:: install +call lerna bootstrap +call lerna add --scope integration-test aws-amplify@^6.0.0 +call lerna add --scope integration-test @aws-amplify/ui-react@^6.0.0 +:: call lerna add --scope integration-test @aws-amplify/datastore +call lerna add --scope integration-test @aws-amplify/codegen-ui +call lerna add --scope integration-test @aws-amplify/codegen-ui-react +call lerna add --scope integration-test @aws-amplify/codegen-ui-test-generator +call lerna add --no-ci --scope integration-test react-router-dom +call lerna add --no-ci --scope integration-test @types/react-router-dom +call lerna add --no-ci --dev --scope integration-test cypress@12.14.0 +call lerna add --no-ci --dev --scope integration-test wait-on +call lerna add --no-ci --scope integration-test os-browserify +call lerna add --no-ci --scope integration-test path-browserify +call lerna add --no-ci --scope integration-test @types/node +call lerna add --no-ci --scope integration-test react-app-rewired + +chdir packages\integration-test +call jq ".scripts.start = \"react-app-rewired start\" | .scripts.build = \"react-app-rewired build\" | .scripts.test = \"react-app-rewired test\"" package.json > tmp.json +move tmp.json package.json diff --git a/scripts/integ-setupv6.sh b/scripts/integ-setupv6.sh new file mode 100755 index 000000000..3842bfce2 --- /dev/null +++ b/scripts/integ-setupv6.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +npm run integ:clean +npm run build + +npx nyc instrument --compact false packages packages/instrumented +rsync -av --delete packages/instrumented/codegen-ui/dist packages/codegen-ui/ +rsync -av --delete packages/instrumented/codegen-ui-react/dist packages/codegen-ui-react/ +rm -r packages/instrumented + +# create +(cd packages && npx -y create-react-app integration-test --use-npm --template typescript) + +# add files +npm run integ:templatesv6 + +# install +lerna bootstrap +lerna add --scope integration-test typescript@4.4.4 +lerna add --scope integration-test aws-amplify@^6.0.0 +lerna add --scope integration-test @aws-amplify/ui-react@^6.0.0 +lerna add --scope integration-test @aws-amplify/datastore +lerna add --scope integration-test @aws-amplify/codegen-ui +lerna add --scope integration-test @aws-amplify/codegen-ui-react +lerna add --scope integration-test @aws-amplify/codegen-ui-test-generator +lerna add --no-ci --scope integration-test react-router-dom +lerna add --no-ci --scope integration-test @types/react-router-dom +lerna add --no-ci --dev --scope integration-test cypress@12.14.0 +lerna add --no-ci --dev --scope integration-test @cypress/code-coverage +lerna add --no-ci --dev --scope integration-test wait-on +lerna add --no-ci --dev --scope integration-test istanbul-lib-report +lerna add --no-ci --dev --scope integration-test istanbul-reports +lerna add --no-ci --scope integration-test os-browserify +lerna add --no-ci --scope integration-test path-browserify +lerna add --no-ci --scope integration-test react-app-rewired + +(cd packages/integration-test && \ + jq '.scripts.start = "react-app-rewired start" | .scripts.build = "react-app-rewired build" | .scripts.test = "react-app-rewired test"' package.json > tmp.json && \ + mv tmp.json package.json \ +) diff --git a/scripts/integ-templatesv6.bat b/scripts/integ-templatesv6.bat new file mode 100644 index 000000000..a630f0851 --- /dev/null +++ b/scripts/integ-templatesv6.bat @@ -0,0 +1,4 @@ +call lerna run build --scope @aws-amplify/codegen-ui-test-generator +call robocopy packages\test-generator\integration-test-templates packages\integration-test /E +set DEPENDENCIES="{\"aws-amplify\": \"6.0.0\"}" +call node packages/test-generator/dist/generators/GenerateTestApp.js diff --git a/scripts/integ-templatesv6.sh b/scripts/integ-templatesv6.sh new file mode 100755 index 000000000..548fdfb6a --- /dev/null +++ b/scripts/integ-templatesv6.sh @@ -0,0 +1,6 @@ +#!/bin/bash +set -e + +lerna run build --scope @aws-amplify/codegen-ui-test-generator +cp -r packages/test-generator/integration-test-templates-amplify-js-v6/. packages/integration-test +DEPENDENCIES='{"aws-amplify": "6.0.0"}' node packages/test-generator/dist/generators/GenerateTestApp.js