From 45dd54545e596d53b447d9d9cd0aa224391aa52c Mon Sep 17 00:00:00 2001
From: Dane Pilcher <dppilche@amazon.com>
Date: Mon, 22 Nov 2021 22:00:34 +0000
Subject: [PATCH] feat: validate property values are not empty

---
 .../validation-helper.test.ts.snap            |  4 ++++
 .../lib/__tests__/validation-helper.test.ts   | 22 +++++++++++++++++++
 packages/codegen-ui/lib/validation-helper.ts  | 21 ++++++++++++++++--
 3 files changed, 45 insertions(+), 2 deletions(-)

diff --git a/packages/codegen-ui/lib/__tests__/__snapshots__/validation-helper.test.ts.snap b/packages/codegen-ui/lib/__tests__/__snapshots__/validation-helper.test.ts.snap
index 514b6073b..1a8ed1504 100644
--- a/packages/codegen-ui/lib/__tests__/__snapshots__/validation-helper.test.ts.snap
+++ b/packages/codegen-ui/lib/__tests__/__snapshots__/validation-helper.test.ts.snap
@@ -12,8 +12,12 @@ exports[`validation-helper validateComponentSchema top-level component requires
 
 exports[`validation-helper validateComponentSchema top-level component requires componentType to be the correct type 1`] = `"componentType must be a \`string\` type, but the final value was: \`2\`."`;
 
+exports[`validation-helper validateComponentSchema top-level component requires non-empty property values 1`] = `"property cannot be empty."`;
+
 exports[`validation-helper validateComponentSchema top-level component requires properties 1`] = `"properties is a required field"`;
 
+exports[`validation-helper validateComponentSchema top-level component requires properties to be the correct type 1`] = `"properties must be a \`object\` type, but the final value was: \`\\"property\\"\`."`;
+
 exports[`validation-helper validateThemeSchema children objects should not be empty 1`] = `"values[1].key is a required field, values[1].value is a required field"`;
 
 exports[`validation-helper validateThemeSchema overrides should contain the right shape 1`] = `"overrides[0].key is a required field"`;
diff --git a/packages/codegen-ui/lib/__tests__/validation-helper.test.ts b/packages/codegen-ui/lib/__tests__/validation-helper.test.ts
index e438ed2e5..3ddde46f4 100644
--- a/packages/codegen-ui/lib/__tests__/validation-helper.test.ts
+++ b/packages/codegen-ui/lib/__tests__/validation-helper.test.ts
@@ -52,6 +52,28 @@ describe('validation-helper', () => {
       }).toThrowErrorMatchingSnapshot();
     });
 
+    test('top-level component requires non-empty property values', () => {
+      expect(() => {
+        validateComponentSchema({
+          componentType: 'View',
+          name: 'MyBindingView',
+          properties: {
+            pathData: {},
+          },
+        });
+      }).toThrowErrorMatchingSnapshot();
+    });
+
+    test('top-level component requires properties to be the correct type', () => {
+      expect(() => {
+        validateComponentSchema({
+          componentType: 'View',
+          name: 'MyBindingView',
+          properties: 'property',
+        });
+      }).toThrowErrorMatchingSnapshot();
+    });
+
     test('top-level component requires componentType to be the correct type', () => {
       expect(() => {
         validateComponentSchema({
diff --git a/packages/codegen-ui/lib/validation-helper.ts b/packages/codegen-ui/lib/validation-helper.ts
index be9159e8c..392e0d43e 100644
--- a/packages/codegen-ui/lib/validation-helper.ts
+++ b/packages/codegen-ui/lib/validation-helper.ts
@@ -26,6 +26,23 @@ const alphaNumNoLeadingNumberString = () => {
     .matches(/^[a-zA-Z][a-zA-Z0-9]*$/, { message: 'Expected an alphanumeric string, starting with a character' });
 };
 
+const propertiesSchema = yup.lazy((value) => {
+  return yup
+    .object()
+    .shape(
+      Object.fromEntries(
+        Object.keys(value || {}).map((key) => [
+          key,
+          yup
+            .object()
+            .test('property', 'property cannot be empty.', (property: Object) => Object.keys(property).length > 0)
+            .required(),
+        ]),
+      ),
+    )
+    .required();
+});
+
 /**
  * Component Schema Definitions
  */
@@ -33,7 +50,7 @@ const studioComponentChildSchema: any = yup.object({
   componentType: alphaNumNoLeadingNumberString().required(),
   // TODO: Name is required in the studio-types file, but doesn't seem to need to be. Relaxing the restriction here.
   name: yup.string().nullable(),
-  properties: yup.object().required(),
+  properties: propertiesSchema,
   // Doing lazy eval here since we reference our own type otherwise
   children: yup.lazy(() => yup.array(studioComponentChildSchema.default(undefined))),
   figmaMetadata: yup.object().nullable(),
@@ -49,7 +66,7 @@ const studioComponentSchema = yup.object({
   id: yup.string().nullable(),
   sourceId: yup.string().nullable(),
   componentType: alphaNumNoLeadingNumberString().required(),
-  properties: yup.object().required(),
+  properties: propertiesSchema,
   children: yup.array(studioComponentChildSchema).nullable(),
   figmaMetadata: yup.object().nullable(),
   variants: yup.array().nullable(),