Create dictionary ui page allows save with insufficient data 08/143508/1
authorFiete Ostkamp <fiete.ostkamp@telekom.de>
Thu, 5 Mar 2026 11:55:03 +0000 (12:55 +0100)
committerFiete Ostkamp <fiete.ostkamp@telekom.de>
Thu, 5 Mar 2026 11:55:03 +0000 (12:55 +0100)
- only enable Save button when required input fields are filled
- add playwright test for that

Issue-ID: CCSDK-4173
Change-Id: Ia9afa49e1428898e0e3975c4d53084a663c85d1e
Signed-off-by: Fiete Ostkamp <fiete.ostkamp@telekom.de>
cds-ui/designer-client/src/app/modules/feature-modules/resource-dictionary/resource-dictionary-creation/dictionary-metadata/dictionary-metadata.component.css
cds-ui/designer-client/src/app/modules/feature-modules/resource-dictionary/resource-dictionary-creation/dictionary-metadata/dictionary-metadata.component.html
cds-ui/designer-client/src/app/modules/feature-modules/resource-dictionary/resource-dictionary-creation/dictionary-metadata/dictionary-metadata.component.ts
cds-ui/designer-client/src/app/modules/feature-modules/resource-dictionary/resource-dictionary-creation/resource-dictionary-creation.component.html
cds-ui/designer-client/src/app/modules/feature-modules/resource-dictionary/resource-dictionary-creation/resource-dictionary-creation.component.ts
cds-ui/e2e-playwright/tests/resource-dictionary-create-validation.spec.ts [new file with mode: 0644]

index ea8d312..67a315d 100644 (file)
@@ -19,9 +19,9 @@
 */-->
 <div class="card creat-card">
     <div class="single-line-model">
-        <label class="label-name">Name</label>
+        <label class="label-name">Name <span class="required-indicator">*</span></label>
         <div class="label-input">
-            <input type="input" [(ngModel)]="metaDataTab.name" placeholder="Dictionary Name">
+            <input type="input" [(ngModel)]="metaDataTab.name" (ngModelChange)="saveMetaDataToStore()" placeholder="Dictionary Name" required>
         </div>
         <!-- <div class="model-note-container error-message">
             Package name already exists with this version. Please enter a different name or enter different version
@@ -33,7 +33,7 @@
         <label class="label-name">Version <span>*</span></label>
         <div class="label-input">
             <input type="input" [readOnly]="!packageNameAndVersionEnables" [(ngModel)]="metaDataTab.version"
-                (input)="validatePackageNameAndVersion()" placeholder="Example: 1.0.0"> 
+                (input)="validatePackageNameAndVersion()" placeholder="Example: 1.0.0">
         </div>
         <div class="model-note-container error-message">{{errorMessage}}</div>
     </div>  -->
         </div>
     </div>
     <div class="single-line-model">
-        <label class="label-name">Data Type</label>
+        <label class="label-name">Data Type <span class="required-indicator">*</span></label>
         <div class="label-input">
-            <input type="input" [(ngModel)]="metaDataTab.property.type" placeholder="Data Type">
+            <input type="input" [(ngModel)]="metaDataTab.property.type" (ngModelChange)="saveMetaDataToStore()" placeholder="Data Type" required>
         </div>
     </div>
     <div class="single-line-model">
-        <label class="label-name">Description</label>
+        <label class="label-name">Description <span class="required-indicator">*</span></label>
         <div class="label-input">
-            <input type="input" [(ngModel)]="metaDataTab.property.description" placeholder="Descripe the package">
+            <input type="input" [(ngModel)]="metaDataTab.property.description" (ngModelChange)="saveMetaDataToStore()" placeholder="Describe the package" required>
         </div>
     </div>
     <div class="single-line-model">
@@ -76,9 +76,9 @@
         </div>
     </div> -->
     <div class="single-line-model">
-        <label class="label-name">Updated By</label>
+        <label class="label-name">Updated By <span class="required-indicator">*</span></label>
         <div class="label-input">
-            <input type="input" [(ngModel)]="metaDataTab['updated-by']" placeholder="Updated By">
+            <input type="input" [(ngModel)]="metaDataTab['updated-by']" (ngModelChange)="saveMetaDataToStore()" placeholder="Updated By">
         </div>
     </div>
 
@@ -94,4 +94,4 @@
                     class="fa fa-times-circle"></i></span>
         </div>
     </div>
-</div>
\ No newline at end of file
+</div>
index 01df118..62b7e18 100644 (file)
@@ -57,6 +57,13 @@ export class DictionaryMetadataComponent implements OnInit {
 
     }
 
+    isFormValid(): boolean {
+        return !!(this.metaDataTab.name && this.metaDataTab.name.trim().length > 0
+            && this.metaDataTab.property.type && this.metaDataTab.property.type.trim().length > 0
+            && this.metaDataTab.property.description && this.metaDataTab.property.description.trim().length > 0
+            && this.metaDataTab['updated-by'] && this.metaDataTab['updated-by'].trim().length > 0);
+    }
+
     // getSources() {
     //     this.dictionaryCreationService.getSources().subscribe(res => {
     //         console.log(res);
index 00d6e31..98bc7a4 100644 (file)
@@ -36,7 +36,7 @@
             <div class="container">
                 <!--Right Side - Action Buttons-->
                 <div class="creat-action-container">
-                    <button class="action-button save" (click)="createDictionary()">
+                    <button class="action-button save" (click)="createDictionary()" [disabled]="isSaveDisabled()">
                         <i class="icon-save-sm" aria-hidden="true"></i>
                         <span>Save</span>
                     </button>
             </div>
         </div>
     </div>
-</div>
\ No newline at end of file
+</div>
index 8eb126d..816c13e 100644 (file)
@@ -67,11 +67,10 @@ export class ResourceDictionaryCreationComponent implements OnInit {
 
   saveDictionaryToStore() {
     this.metadataTabComponent.saveMetaDataToStore();
-    // console.log('00000000000');
-    // this.dictionaryCreationStore.getSources();
-    // this.dictionaryCreationStore.state$.subscribe(dd => {
-    //   console.log(dd);
-    // });
+  }
+
+  isSaveDisabled(): boolean {
+    return !this.metadataTabComponent || !this.metadataTabComponent.isFormValid();
   }
 
   createDictionary() {
diff --git a/cds-ui/e2e-playwright/tests/resource-dictionary-create-validation.spec.ts b/cds-ui/e2e-playwright/tests/resource-dictionary-create-validation.spec.ts
new file mode 100644 (file)
index 0000000..73ad49d
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * resource-dictionary-create-validation.spec.ts
+ *
+ * End-to-end tests for the Resource Dictionary creation form validation.
+ *
+ * These tests verify:
+ *   1. Save button is disabled when required fields are empty.
+ *   2. Required field indicators are shown (Name, Data Type, Description, Updated By).
+ *   3. Save button becomes enabled once all required fields are filled.
+ *   4. Backend returns 400 Bad Request when required fields are missing.
+ */
+
+import { test, expect } from '@playwright/test';
+
+test.describe('Resource Dictionary – create form validation', () => {
+    test.beforeEach(async ({ page }) => {
+        await page.goto('/#/resource-dictionary/createDictionary');
+        await page.waitForLoadState('networkidle');
+    });
+
+    test('save button is disabled when no fields are filled', async ({ page }) => {
+        const saveButton = page.locator('button.action-button.save');
+        await expect(saveButton).toBeVisible({ timeout: 10_000 });
+        await expect(saveButton).toBeDisabled();
+    });
+
+    test('required field indicators are shown for Name, Data Type, Description, Updated By', async ({ page }) => {
+        const metadata = page.locator('app-dictionary-metadata');
+        await expect(metadata).toBeAttached({ timeout: 10_000 });
+
+        const requiredIndicators = metadata.locator('.required-indicator');
+        await expect(requiredIndicators).toHaveCount(4);
+    });
+
+    test('save button remains disabled when only some required fields are filled', async ({ page }) => {
+        const metadata = page.locator('app-dictionary-metadata');
+        await expect(metadata).toBeAttached({ timeout: 10_000 });
+
+        // Fill only the Name field
+        const nameInput = metadata.locator('input[placeholder="Dictionary Name"]');
+        await nameInput.fill('test-dictionary');
+
+        const saveButton = page.locator('button.action-button.save');
+        await expect(saveButton).toBeDisabled();
+    });
+
+    test('save button becomes enabled when all required fields are filled', async ({ page }) => {
+        const metadata = page.locator('app-dictionary-metadata');
+        await expect(metadata).toBeAttached({ timeout: 10_000 });
+
+        // Fill all required fields
+        await metadata.locator('input[placeholder="Dictionary Name"]').fill('test-dictionary');
+        await metadata.locator('input[placeholder="Data Type"]').fill('string');
+        await metadata.locator('input[placeholder="Describe the package"]').fill('A test dictionary');
+        await metadata.locator('input[placeholder="Updated By"]').fill('test-user@example.com');
+
+        const saveButton = page.locator('button.action-button.save');
+        await expect(saveButton).toBeEnabled({ timeout: 5_000 });
+    });
+
+    test('save button becomes disabled again when a required field is cleared', async ({ page }) => {
+        const metadata = page.locator('app-dictionary-metadata');
+        await expect(metadata).toBeAttached({ timeout: 10_000 });
+
+        // Fill all required fields
+        await metadata.locator('input[placeholder="Dictionary Name"]').fill('test-dictionary');
+        await metadata.locator('input[placeholder="Data Type"]').fill('string');
+        await metadata.locator('input[placeholder="Describe the package"]').fill('A test dictionary');
+        await metadata.locator('input[placeholder="Updated By"]').fill('test-user@example.com');
+
+        const saveButton = page.locator('button.action-button.save');
+        await expect(saveButton).toBeEnabled({ timeout: 5_000 });
+
+        // Clear the Name field
+        await metadata.locator('input[placeholder="Dictionary Name"]').fill('');
+
+        await expect(saveButton).toBeDisabled({ timeout: 5_000 });
+    });
+});
+
+test.describe('Resource Dictionary – backend validation via mock', () => {
+    test('POST /api/v1/dictionary/definition with empty body returns 400', async ({ request }) => {
+        const resp = await request.post('http://localhost:8080/api/v1/dictionary/definition', {
+            data: {},
+            headers: { 'Content-Type': 'application/json' },
+        });
+        expect(resp.status()).toBe(400);
+        const body = await resp.json();
+        expect(body.message).toContain('name is missing');
+    });
+
+    test('POST /api/v1/dictionary/definition with partial body returns 400', async ({ request }) => {
+        const resp = await request.post('http://localhost:8080/api/v1/dictionary/definition', {
+            data: {
+                name: 'test-dict',
+                property: { type: '', description: '' },
+                'updated-by': '',
+            },
+            headers: { 'Content-Type': 'application/json' },
+        });
+        expect(resp.status()).toBe(400);
+        const body = await resp.json();
+        expect(body.message).toContain('description is missing');
+    });
+
+    test('POST /api/v1/dictionary/definition with valid body returns 200', async ({ request }) => {
+        const resp = await request.post('http://localhost:8080/api/v1/dictionary/definition', {
+            data: {
+                name: 'test-dict',
+                property: { type: 'string', description: 'A test dictionary' },
+                'updated-by': 'test-user@example.com',
+            },
+            headers: { 'Content-Type': 'application/json' },
+        });
+        expect(resp.status()).toBe(200);
+    });
+});