From 6887c137d752bd5aa0186c027cccc60f9a0852d7 Mon Sep 17 00:00:00 2001 From: Fiete Ostkamp Date: Thu, 5 Mar 2026 12:55:03 +0100 Subject: [PATCH] Create dictionary ui page allows save with insufficient data - 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 --- .../dictionary-metadata.component.css | 6 +- .../dictionary-metadata.component.html | 20 ++-- .../dictionary-metadata.component.ts | 7 ++ .../resource-dictionary-creation.component.html | 4 +- .../resource-dictionary-creation.component.ts | 9 +- .../resource-dictionary-create-validation.spec.ts | 117 +++++++++++++++++++++ 6 files changed, 145 insertions(+), 18 deletions(-) create mode 100644 cds-ui/e2e-playwright/tests/resource-dictionary-create-validation.spec.ts diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/resource-dictionary/resource-dictionary-creation/dictionary-metadata/dictionary-metadata.component.css b/cds-ui/designer-client/src/app/modules/feature-modules/resource-dictionary/resource-dictionary-creation/dictionary-metadata/dictionary-metadata.component.css index f263c0086..0d0327790 100644 --- a/cds-ui/designer-client/src/app/modules/feature-modules/resource-dictionary/resource-dictionary-creation/dictionary-metadata/dictionary-metadata.component.css +++ b/cds-ui/designer-client/src/app/modules/feature-modules/resource-dictionary/resource-dictionary-creation/dictionary-metadata/dictionary-metadata.component.css @@ -16,4 +16,8 @@ * See the License for the specific language governing permissions and * limitations under the License. * ============LICENSE_END========================================================= -*/ \ No newline at end of file +*/ +.required-indicator { + color: #dc3545; + font-weight: bold; +} diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/resource-dictionary/resource-dictionary-creation/dictionary-metadata/dictionary-metadata.component.html b/cds-ui/designer-client/src/app/modules/feature-modules/resource-dictionary/resource-dictionary-creation/dictionary-metadata/dictionary-metadata.component.html index ea8d31223..67a315d0c 100644 --- a/cds-ui/designer-client/src/app/modules/feature-modules/resource-dictionary/resource-dictionary-creation/dictionary-metadata/dictionary-metadata.component.html +++ b/cds-ui/designer-client/src/app/modules/feature-modules/resource-dictionary/resource-dictionary-creation/dictionary-metadata/dictionary-metadata.component.html @@ -19,9 +19,9 @@ */-->
- +
- +
@@ -44,15 +44,15 @@
- +
- +
- +
- +
@@ -76,9 +76,9 @@
-->
- +
- +
@@ -94,4 +94,4 @@ class="fa fa-times-circle"> - \ No newline at end of file + diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/resource-dictionary/resource-dictionary-creation/dictionary-metadata/dictionary-metadata.component.ts b/cds-ui/designer-client/src/app/modules/feature-modules/resource-dictionary/resource-dictionary-creation/dictionary-metadata/dictionary-metadata.component.ts index 01df118d2..62b7e18de 100644 --- a/cds-ui/designer-client/src/app/modules/feature-modules/resource-dictionary/resource-dictionary-creation/dictionary-metadata/dictionary-metadata.component.ts +++ b/cds-ui/designer-client/src/app/modules/feature-modules/resource-dictionary/resource-dictionary-creation/dictionary-metadata/dictionary-metadata.component.ts @@ -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); diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/resource-dictionary/resource-dictionary-creation/resource-dictionary-creation.component.html b/cds-ui/designer-client/src/app/modules/feature-modules/resource-dictionary/resource-dictionary-creation/resource-dictionary-creation.component.html index 00d6e31d3..98bc7a448 100644 --- a/cds-ui/designer-client/src/app/modules/feature-modules/resource-dictionary/resource-dictionary-creation/resource-dictionary-creation.component.html +++ b/cds-ui/designer-client/src/app/modules/feature-modules/resource-dictionary/resource-dictionary-creation/resource-dictionary-creation.component.html @@ -36,7 +36,7 @@
- @@ -139,4 +139,4 @@
- \ No newline at end of file + diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/resource-dictionary/resource-dictionary-creation/resource-dictionary-creation.component.ts b/cds-ui/designer-client/src/app/modules/feature-modules/resource-dictionary/resource-dictionary-creation/resource-dictionary-creation.component.ts index 8eb126d21..816c13e2c 100644 --- a/cds-ui/designer-client/src/app/modules/feature-modules/resource-dictionary/resource-dictionary-creation/resource-dictionary-creation.component.ts +++ b/cds-ui/designer-client/src/app/modules/feature-modules/resource-dictionary/resource-dictionary-creation/resource-dictionary-creation.component.ts @@ -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 index 000000000..73ad49da3 --- /dev/null +++ b/cds-ui/e2e-playwright/tests/resource-dictionary-create-validation.spec.ts @@ -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); + }); +}); -- 2.16.6